Merge "Use turn_slow_filters_off method to ensure frame rate can be test smoothly" into pie-cts-dev
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index 61ec7e1..d75532b 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -513,6 +513,19 @@
     return False
 
 
+def backward_compatible(props):
+    """Returns whether a device supports BACKWARD_COMPATIBLE.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return props.has_key("android.request.availableCapabilities") and \
+              0 in props["android.request.availableCapabilities"]
+
+
 class __UnitTest(unittest.TestCase):
     """Run a suite of unit tests on this module.
     """
diff --git a/apps/CameraITS/tests/scene0/test_burst_capture.py b/apps/CameraITS/tests/scene0/test_burst_capture.py
index f915a6a..c573584 100644
--- a/apps/CameraITS/tests/scene0/test_burst_capture.py
+++ b/apps/CameraITS/tests/scene0/test_burst_capture.py
@@ -12,13 +12,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
-import its.device
-import its.objects
 import os.path
 
+import its.caps
+import its.device
+import its.image
+import its.objects
+
+
 def main():
     """Test capture a burst of full size images is fast enough to not timeout.
+
        This test verify that entire capture pipeline can keep up the speed
        of fullsize capture + CPU read for at least some time.
     """
@@ -27,6 +31,7 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.backward_compatible(props))
         req = its.objects.auto_capture_request()
         caps = cam.do_capture([req]*NUM_TEST_FRAMES)
 
diff --git a/apps/CameraITS/tests/scene0/test_camera_properties.py b/apps/CameraITS/tests/scene0/test_camera_properties.py
index eb638f0..dbd528d 100644
--- a/apps/CameraITS/tests/scene0/test_camera_properties.py
+++ b/apps/CameraITS/tests/scene0/test_camera_properties.py
@@ -26,8 +26,6 @@
 
         pprint.pprint(props)
 
-        its.caps.skip_unless(its.caps.manual_sensor(props))
-
         # Test that a handful of required keys are present.
         assert(props.has_key('android.sensor.info.sensitivityRange'))
         assert(props.has_key('android.sensor.orientation'))
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index 48ce28e..b8949b1 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -31,6 +31,7 @@
         # Arbitrary capture request exposure values; image content is not
         # important for this test, only the metadata.
         props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.backward_compatible(props))
         auto_req = its.objects.auto_capture_request()
         cap = cam.do_capture(auto_req)
         md = cap["metadata"]
diff --git a/apps/CameraITS/tests/scene0/test_unified_timestamps.py b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
index ae4583f..5a9228e 100644
--- a/apps/CameraITS/tests/scene0/test_unified_timestamps.py
+++ b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
@@ -25,7 +25,8 @@
         props = cam.get_camera_properties()
 
         # Only run test if the appropriate caps are claimed.
-        its.caps.skip_unless(its.caps.sensor_fusion(props))
+        its.caps.skip_unless(its.caps.sensor_fusion(props) and
+                             its.caps.backward_compatible(props))
 
         # Get the timestamp of a captured image.
         if its.caps.manual_sensor(props):
diff --git a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
index 92dfd0d..a46d54c 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -334,8 +334,8 @@
                 h_iter = size_iter[1]
                 # Skip testing same format/size combination
                 # ITS does not handle that properly now
-                if (dual_target and w_iter == size_cmpr[0]
-                            and h_iter == size_cmpr[1]
+                if (dual_target
+                            and w_iter*h_iter == size_cmpr[0]*size_cmpr[1]
                             and fmt_iter == fmt_cmpr):
                     continue
                 out_surface = [{"width": w_iter,
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index b605cb4..7d72e2d 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1304,9 +1304,8 @@
     <string name="no_camera_manager">
         No camera manager exists!  This test device is in a bad state.
     </string>
-    <string name="all_legacy_devices">
-        All cameras on this device are LEGACY mode only - ITS tests are only required on LIMITED
-        or better devices.  Pass.
+    <string name="all_exempted_devices">
+        All cameras on this device are exempted from ITS - Pass.
     </string>
     <string name="its_test_passed">All Camera ITS tests passed.  Pass button enabled!</string>
     <string name="its_test_failed">Some Camera ITS tests failed.</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index db45452..fe1c0ed 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -346,17 +346,16 @@
         }
     }
 
-    public void openCameraDevice(int cameraId) throws ItsException {
-        Logt.i(TAG, String.format("Opening camera %d", cameraId));
+    public void openCameraDevice(String cameraId) throws ItsException {
+        Logt.i(TAG, String.format("Opening camera %s", cameraId));
 
-        String[] devices;
         try {
-            devices = mCameraManager.getCameraIdList();
-            if (devices == null || devices.length == 0) {
-                throw new ItsException("No camera devices");
-            }
             if (mMemoryQuota == -1) {
                 // Initialize memory quota on this device
+                List<String> devices = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
+                if (devices.size() == 0) {
+                    throw new ItsException("No camera devices");
+                }
                 for (String camId : devices) {
                     CameraCharacteristics chars =  mCameraManager.getCameraCharacteristics(camId);
                     Size maxYuvSize = ItsUtils.getMaxOutputSize(
@@ -373,10 +372,8 @@
         }
 
         try {
-            mCamera = mBlockingCameraManager.openCamera(devices[cameraId],
-                    mCameraListener, mCameraHandler);
-            mCameraCharacteristics = mCameraManager.getCameraCharacteristics(
-                    devices[cameraId]);
+            mCamera = mBlockingCameraManager.openCamera(cameraId, mCameraListener, mCameraHandler);
+            mCameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
             mSocketQueueQuota = new Semaphore(mMemoryQuota, true);
         } catch (CameraAccessException e) {
             throw new ItsException("Failed to open camera", e);
@@ -646,7 +643,7 @@
                 JSONObject cmdObj = new JSONObject(cmd);
                 Logt.i(TAG, "Start processing command" + cmdObj.getString("cmdName"));
                 if ("open".equals(cmdObj.getString("cmdName"))) {
-                    int cameraId = cmdObj.getInt("cameraId");
+                    String cameraId = cmdObj.getString("cameraId");
                     openCameraDevice(cameraId);
                 } else if ("close".equals(cmdObj.getString("cmdName"))) {
                     closeCameraDevice();
@@ -901,6 +898,9 @@
     private void doGetPropsById(JSONObject params) throws ItsException {
         String[] devices;
         try {
+            // Intentionally not using ItsUtils.getItsCompatibleCameraIds here so it's possible to
+            // write some simple script to query camera characteristics even for devices exempted
+            // from ITS today.
             devices = mCameraManager.getCameraIdList();
             if (devices == null || devices.length == 0) {
                 throw new ItsException("No camera devices");
@@ -927,34 +927,21 @@
     }
 
     private void doGetCameraIds() throws ItsException {
-        String[] devices;
-        try {
-            devices = mCameraManager.getCameraIdList();
-            if (devices == null || devices.length == 0) {
-                throw new ItsException("No camera devices");
-            }
-        } catch (CameraAccessException e) {
-            throw new ItsException("Failed to get device ID list", e);
+        List<String> devices = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
+        if (devices.size() == 0) {
+            throw new ItsException("No camera devices");
         }
 
         try {
             JSONObject obj = new JSONObject();
             JSONArray array = new JSONArray();
             for (String id : devices) {
-                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
-                // Only supply camera Id for non-legacy cameras since legacy camera does not
-                // support ITS
-                if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) !=
-                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
-                    array.put(id);
-                }
+                array.put(id);
             }
             obj.put("cameraIdArray", array);
             mSocketRunnableObj.sendResponse("cameraIds", obj);
         } catch (org.json.JSONException e) {
             throw new ItsException("JSON error: ", e);
-        } catch (android.hardware.camera2.CameraAccessException e) {
-            throw new ItsException("Access error: ", e);
         }
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index d6feb51..fd62ed2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -21,7 +21,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
-import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraManager;
 import android.os.Bundle;
@@ -80,7 +79,7 @@
     private boolean mReceiverRegistered = false;
 
     // Initialized in onCreate
-    ArrayList<String> mToBeTestedCameraIds = null;
+    List<String> mToBeTestedCameraIds = null;
 
     // Scenes
     private static final ArrayList<String> mSceneIds = new ArrayList<String> () { {
@@ -333,32 +332,20 @@
         // Hide the test if all camera devices are legacy
         CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
         try {
-            String[] cameraIds = manager.getCameraIdList();
-            mToBeTestedCameraIds = new ArrayList<String>();
-            for (String id : cameraIds) {
-                CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
-                int hwLevel = characteristics.get(
-                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
-                if (hwLevel
-                        != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY &&
-                        hwLevel
-                        != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) {
-                    mToBeTestedCameraIds.add(id);
-                }
-            }
-            if (mToBeTestedCameraIds.size() == 0) {
-                showToast(R.string.all_legacy_devices);
-                ItsTestActivity.this.getReportLog().setSummary(
-                        "PASS: all cameras on this device are LEGACY or EXTERNAL"
-                        , 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
-                setTestResultAndFinish(true);
-            }
-        } catch (CameraAccessException e) {
+            mToBeTestedCameraIds = ItsUtils.getItsCompatibleCameraIds(manager);
+        } catch (ItsException e) {
             Toast.makeText(ItsTestActivity.this,
                     "Received error from camera service while checking device capabilities: "
                             + e, Toast.LENGTH_SHORT).show();
         }
 
+        if (mToBeTestedCameraIds.size() == 0) {
+            showToast(R.string.all_exempted_devices);
+            ItsTestActivity.this.getReportLog().setSummary(
+                    "PASS: all cameras on this device are exempted from ITS"
+                    , 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
+            setTestResultAndFinish(true);
+        }
         super.onCreate(savedInstanceState);
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
index 65e4970..41ae288 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
@@ -19,8 +19,10 @@
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.params.MeteringRectangle;
@@ -296,4 +298,47 @@
                 return false;
         }
     }
+
+    public static List<String> getItsCompatibleCameraIds(CameraManager manager)
+            throws ItsException {
+        if (manager == null) {
+            throw new IllegalArgumentException("CameraManager is null");
+        }
+
+        ArrayList<String> outList = new ArrayList<String>();
+        try {
+            String[] cameraIds = manager.getCameraIdList();
+            for (String id : cameraIds) {
+                CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
+                int[] actualCapabilities = characteristics.get(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+                boolean haveBC = false;
+                final int BACKWARD_COMPAT =
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
+                for (int capability : actualCapabilities) {
+                    if (capability == BACKWARD_COMPAT) {
+                        haveBC = true;
+                        break;
+                    }
+                }
+
+                // Skip devices that does not support BACKWARD_COMPATIBLE capability
+                if (!haveBC) continue;
+
+                int hwLevel = characteristics.get(
+                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+                if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY ||
+                        hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) {
+                    // Skip LEGACY and EXTERNAL devices
+                    continue;
+                }
+                outList.add(id);
+            }
+        } catch (CameraAccessException e) {
+            Logt.e(TAG,
+                    "Received error from camera service while checking device capabilities: " + e);
+            throw new ItsException("Failed to get device ID list", e);
+        }
+        return outList;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MotionIndicatorView.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MotionIndicatorView.java
index 14784dd..4160572 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MotionIndicatorView.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MotionIndicatorView.java
@@ -26,6 +26,7 @@
 import android.hardware.SensorManager;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Surface;
 import android.view.View;
 
 /**
@@ -89,6 +90,8 @@
 
     private boolean mXEnabled, mYEnabled, mZEnabled;
 
+    private boolean mIsDeviceRotated = false;
+
     /**
      * Constructor
      * @param context
@@ -146,6 +149,15 @@
     }
 
     /**
+     * Set the device's current rotation
+     * @param rotation Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180, or
+     *                 Surface.ROTATION_270
+     */
+    public void setDeviceRotation(int rotation) {
+        mIsDeviceRotated = (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+    }
+
+    /**
      * Set the active axis for display
      *
      * @param axis AXIS_X, AXIS_Y, AXIS_Z for x, y, z axis indicators, or AXIS_ALL for all three.
@@ -181,16 +193,22 @@
         mXSize = w;
         mYSize = h;
 
-        mZBoundOut = new RectF(w/2-w/2.5f, h/2-w/2.5f, w/2+w/2.5f, h/2+w/2.5f);
+        float halfSideLength = 0.4f * Math.min(w, h);
+        float leftSide = w/2 - halfSideLength;
+        float topSide = h/2 - halfSideLength;
+        float rightSide = w/2 + halfSideLength;
+        float bottomSide = h/2 + halfSideLength;
+
+        mZBoundOut = new RectF(leftSide, topSide, rightSide, bottomSide);
         mZBoundOut2 = new RectF(
-                w/2-w/2.5f-ZRING_CURSOR_ADD, h/2-w/2.5f-ZRING_CURSOR_ADD,
-                w/2+w/2.5f+ZRING_CURSOR_ADD, h/2+w/2.5f+ZRING_CURSOR_ADD);
+                leftSide-ZRING_CURSOR_ADD, topSide-ZRING_CURSOR_ADD,
+                rightSide+ZRING_CURSOR_ADD, bottomSide+ZRING_CURSOR_ADD);
         mZBoundIn = new RectF(
-                w/2-w/2.5f+ZRING_WIDTH, h/2-w/2.5f+ZRING_WIDTH,
-                w/2+w/2.5f-ZRING_WIDTH, h/2+w/2.5f-ZRING_WIDTH);
+                leftSide+ZRING_WIDTH, topSide+ZRING_WIDTH,
+                rightSide-ZRING_WIDTH, bottomSide-ZRING_WIDTH);
         mZBoundIn2 = new RectF(
-                w/2-w/2.5f+ZRING_WIDTH+ZRING_CURSOR_ADD, h/2-w/2.5f+ZRING_WIDTH+ZRING_CURSOR_ADD,
-                w/2+w/2.5f-ZRING_WIDTH-ZRING_CURSOR_ADD, h/2+w/2.5f-ZRING_WIDTH-ZRING_CURSOR_ADD);
+                leftSide+ZRING_WIDTH+ZRING_CURSOR_ADD, topSide+ZRING_WIDTH+ZRING_CURSOR_ADD,
+                rightSide-ZRING_WIDTH-ZRING_CURSOR_ADD, bottomSide-ZRING_WIDTH-ZRING_CURSOR_ADD);
 
         if (LOCAL_LOGV) Log.v(TAG, "New view size = ("+w+", "+h+")");
     }
@@ -209,8 +227,14 @@
         p.setColor(Color.YELLOW);
         canvas.drawRect(10,10, 50, 50, p);
 
-        if (mXEnabled && mXCovered != null) {
-            int xNStep = mXCovered.getNSteps() + 4; // two on each side as a buffer
+        // In order to determine which progress bar to draw, the device's rotation must be accounted
+        // for since the accelerometer rotates with the display.
+        boolean drawX = (mXEnabled && !mIsDeviceRotated) || (mYEnabled && mIsDeviceRotated);
+        boolean drawY = (mYEnabled && !mIsDeviceRotated) || (mXEnabled && mIsDeviceRotated);
+
+        if (drawX && mXCovered != null) {
+            RangeCoveredRegister covered = mIsDeviceRotated ? mYCovered : mXCovered;
+            int xNStep = covered.getNSteps() + 4; // two on each side as a buffer
             int xStepSize = mXSize * 3/4 / xNStep;
             int xLeft = mXSize * 1/8 + (mXSize * 3/4 % xNStep)/2;
 
@@ -219,8 +243,8 @@
                     xLeft+xStepSize*xNStep-1, XBAR_WIDTH+XBAR_MARGIN, mRangePaint);
 
             // covered range
-            for (i=0; i<mXCovered.getNSteps(); ++i) {
-                if (mXCovered.isCovered(i)) {
+            for (i=0; i<covered.getNSteps(); ++i) {
+                if (covered.isCovered(i)) {
                     canvas.drawRect(
                             xLeft+xStepSize*(i+2), XBAR_MARGIN,
                             xLeft+xStepSize*(i+3)-1, XBAR_WIDTH + XBAR_MARGIN,
@@ -235,12 +259,14 @@
                     xLeft+xStepSize*(xNStep-2)+3, XBAR_WIDTH+XBAR_MARGIN, mLimitPaint);
 
             // cursor
-            t = (int)(xLeft+xStepSize*(mXCovered.getLastValue()+2));
+            t = (int)(xLeft+xStepSize*(covered.getLastValue()+2));
             canvas.drawRect(t-4, XBAR_MARGIN-XBAR_CURSOR_ADD, t+3,
                     XBAR_WIDTH+XBAR_MARGIN+XBAR_CURSOR_ADD, mCursorPaint);
         }
-        if (mYEnabled && mYCovered != null) {
-            int yNStep = mYCovered.getNSteps() + 4; // two on each side as a buffer
+
+        if (drawY && mYCovered != null) {
+            RangeCoveredRegister covered = mIsDeviceRotated ? mXCovered : mYCovered;
+            int yNStep = covered.getNSteps() + 4; // two on each side as a buffer
             int yStepSize = mYSize * 3/4 / yNStep;
             int yLeft = mYSize * 1/8 + (mYSize * 3/4 % yNStep)/2;
 
@@ -249,8 +275,8 @@
                     YBAR_WIDTH+YBAR_MARGIN, yLeft+yStepSize*yNStep-1, mRangePaint);
 
             // covered range
-            for (i=0; i<mYCovered.getNSteps(); ++i) {
-                if (mYCovered.isCovered(i)) {
+            for (i=0; i<covered.getNSteps(); ++i) {
+                if (covered.isCovered(i)) {
                     canvas.drawRect(
                             YBAR_MARGIN, yLeft+yStepSize*(i+2),
                             YBAR_WIDTH + YBAR_MARGIN, yLeft+yStepSize*(i+3)-1,
@@ -265,7 +291,7 @@
                     YBAR_WIDTH + YBAR_MARGIN, yLeft + yStepSize * (yNStep - 2) + 3, mLimitPaint);
 
             // cursor
-            t = (int)(yLeft+yStepSize*(mYCovered.getLastValue()+2));
+            t = (int)(yLeft+yStepSize*(covered.getLastValue()+2));
             canvas.drawRect( YBAR_MARGIN-YBAR_CURSOR_ADD, t-4,
                     YBAR_WIDTH+YBAR_MARGIN+YBAR_CURSOR_ADD, t+3, mCursorPaint);
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVCameraPreview.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVCameraPreview.java
index bd463af..10d1865 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVCameraPreview.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVCameraPreview.java
@@ -21,9 +21,11 @@
 import android.hardware.Camera;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 
 import java.io.IOException;
 import java.lang.Math;
@@ -36,10 +38,11 @@
     private static final String TAG = "RVCVCameraPreview";
     private static final boolean LOCAL_LOGD = true;
 
+    private Context mContext = null;
     private SurfaceHolder mHolder;
     private Camera mCamera;
-    private float mAspect;
-    private int mRotation;
+    private float mCameraAspectRatio = 0;
+    private int mCameraRotation = 0;
     private boolean mCheckStartTest = false;
     private boolean mPreviewStarted = false;
 
@@ -51,6 +54,7 @@
      */
     public RVCVCameraPreview(Context context) {
         super(context);
+        mContext = context;
         mCamera = null;
         initSurface();
     }
@@ -62,12 +66,13 @@
      */
     public RVCVCameraPreview(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mContext = context;
     }
 
     public void init(Camera camera, float aspectRatio, int rotation)  {
         this.mCamera = camera;
-        mAspect = aspectRatio;
-        mRotation = rotation;
+        mCameraAspectRatio = aspectRatio;
+        mCameraRotation = rotation;
         initSurface();
     }
 
@@ -111,7 +116,11 @@
             // preview surface or camera does not exist
             return;
         }
-        if (adjustLayoutParamsIfNeeded()) {
+
+        int totalRotation = getRequiredRotation();
+        mCamera.setDisplayOrientation(totalRotation);
+
+        if (adjustLayoutParamsIfNeeded(totalRotation)) {
             // Wait on next surfaceChanged() call before proceeding
             Log.d(TAG, "Waiting on surface change before starting preview");
             return;
@@ -127,7 +136,6 @@
         }
         mCheckStartTest = false;
 
-        mCamera.setDisplayOrientation(mRotation);
         try {
             mCamera.setPreviewDisplay(holder);
             mCamera.startPreview();
@@ -142,23 +150,70 @@
     }
 
     /**
+     * Determine the rotation required to display the camera's preview on the screen as large as
+     * possible. This function combines the device's current rotation from its default orientation
+     * and the rotation of the camera.
+     */
+    private int getRequiredRotation() {
+        WindowManager windowManager =
+                (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        int deviceRotation = 0;
+        if (windowManager != null) {
+            switch (windowManager.getDefaultDisplay().getRotation()) {
+                case Surface.ROTATION_0:
+                    deviceRotation = 0;
+                    break;
+                case Surface.ROTATION_90:
+                    deviceRotation = 270;
+                    break;
+                case Surface.ROTATION_180:
+                    deviceRotation = 180;
+                    break;
+                case Surface.ROTATION_270:
+                    deviceRotation = 90;
+                    break;
+                default:
+                    deviceRotation = 0;
+                    break;
+            }
+        } else {
+            Log.w(TAG, "Unable to get device rotation, preview may be skewed.");
+        }
+
+        return (mCameraRotation + deviceRotation) % 360;
+    }
+
+    /**
      * Resize the layout to more closely match the desired aspect ratio, if necessary.
      *
      * @return true if we updated the layout params, false if the params look good
      */
-    private boolean adjustLayoutParamsIfNeeded() {
+    private boolean adjustLayoutParamsIfNeeded(int totalRotation) {
+        // Determine the maximum size layout that maintains the camera's preview aspect ratio
+        float cameraAspect = mCameraAspectRatio;
+
+        // Check the camera and device rotation and invert the aspect ratio if the device is not
+        // rotated at 0 or 180 degrees.
+        if (totalRotation % 180 != 0) {
+            // The device is rotated, so the screen should be the inverse of the aspect ratio
+            cameraAspect = 1.0f / mCameraAspectRatio;
+        }
+
+        // Only adjust if there is at least 1% error between the aspects
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
         int curWidth = getWidth();
         int curHeight = getHeight();
-        float curAspect = (float)curHeight / (float)curWidth;
-        float aspectDelta = Math.abs(mAspect - curAspect);
-        if ((aspectDelta / mAspect) >= 0.01) {
-            if (curAspect > mAspect) {
-                layoutParams.height = (int)Math.round(curWidth * mAspect);
+        float curAspect = (float)curWidth / (float)curHeight;
+        float aspectDelta = Math.abs(cameraAspect - curAspect);
+        if ((aspectDelta / cameraAspect) >= 0.01) {
+            if (cameraAspect > curAspect) {
+                // Camera preview is wider than the current layout. Need to shorten the current layout
                 layoutParams.width = curWidth;
+                layoutParams.height = (int)(curWidth / cameraAspect);
             } else {
+                // Camera preview taller than the current layout. Need to narrow the current layout
+                layoutParams.width = (int)(curHeight * cameraAspect);
                 layoutParams.height = curHeight;
-                layoutParams.width = (int)Math.round(curHeight / mAspect);
             }
 
             if (layoutParams.height != curHeight || layoutParams.width != curWidth) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVRecordActivity.java
index 8199736..4f92b64 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVRecordActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVRecordActivity.java
@@ -33,7 +33,9 @@
 import android.os.Environment;
 import android.util.JsonWriter;
 import android.util.Log;
+import android.view.Surface;
 import android.view.Window;
+import android.view.WindowManager;
 import android.widget.ImageView;
 import android.widget.Toast;
 
@@ -70,6 +72,7 @@
     private RVSensorLogger          mRVSensorLogger;
     private CoverageManager         mCoverManager;
     private CameraContext mCameraContext;
+    private int mDeviceRotation = Surface.ROTATION_0;
 
     public static final int AXIS_NONE = 0;
     public static final int AXIS_ALL = SensorManager.AXIS_X +
@@ -119,6 +122,12 @@
 
         // locate views
         mIndicatorView = (MotionIndicatorView) findViewById(R.id.cam_indicator);
+        WindowManager windowManager =
+                (WindowManager)getSystemService(Context.WINDOW_SERVICE);
+        if (windowManager != null) {
+            mDeviceRotation = windowManager.getDefaultDisplay().getRotation();
+            mIndicatorView.setDeviceRotation(mDeviceRotation);
+        }
 
         initStoragePath();
     }
@@ -224,6 +233,9 @@
 
         if (axis >=SensorManager.AXIS_X && axis <=SensorManager.AXIS_Z) {
             imageView.setImageResource(prompts[axis-1]);
+            if (mDeviceRotation != Surface.ROTATION_0 && mDeviceRotation != Surface.ROTATION_180) {
+                imageView.setRotation(90);
+            }
             mIndicatorView.enableAxis(axis);
             mRVSensorLogger.updateRegister(mCoverManager.getAxis(axis), axis);
             notifyPrompt(axis);
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java b/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
old mode 100644
new mode 100755
index 301a626..c26ddd0
--- a/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
@@ -43,7 +43,7 @@
 public class BlockingBroadcastReceiver extends BroadcastReceiver {
     private static final String TAG = "BlockingBroadcast";
 
-    private static final int DEFAULT_TIMEOUT_SECONDS = 10;
+    private static final int DEFAULT_TIMEOUT_SECONDS = 30;
 
     private final BlockingQueue<Intent> mBlockingQueue;
     private final String mExpectedAction;
@@ -68,7 +68,7 @@
 
     /**
      * Wait until the broadcast and return the received broadcast intent. {@code null} is returned
-     * if no broadcast with expected action is received within 10 seconds.
+     * if no broadcast with expected action is received within 30 seconds.
      */
     public @Nullable Intent awaitForBroadcast() {
         try {
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
index d2eea34..afcaae0 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
@@ -146,7 +146,8 @@
 
     protected boolean supportedHardwareForScopedDirectoryAccess() {
         final PackageManager pm = getInstrumentation().getContext().getPackageManager();
-        if (pm.hasSystemFeature("android.hardware.type.watch")) {
+        if (pm.hasSystemFeature("android.hardware.type.watch")
+                || pm.hasSystemFeature("android.hardware.type.automotive")) {
             return false;
         }
         return true;
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
index 62a05c8..a54b9b5 100755
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
@@ -64,8 +64,8 @@
 public abstract class BasePermissionsTest {
     private static final String PLATFORM_PACKAGE_NAME = "android";
 
-    private static final long IDLE_TIMEOUT_MILLIS = 500;
-    private static final long GLOBAL_TIMEOUT_MILLIS = 5000;
+    private static final long IDLE_TIMEOUT_MILLIS = 1000;
+    private static final long GLOBAL_TIMEOUT_MILLIS = 10000;
 
     private static final long RETRY_TIMEOUT = 3 * GLOBAL_TIMEOUT_MILLIS;
     private static final String LOG_TAG = "BasePermissionsTest";
@@ -557,7 +557,9 @@
             throws Exception {
         AccessibilityNodeInfo result = current;
         while (result != null) {
-            if (result.getCollectionItemInfo() != null) {
+            // Nodes that are in the hierarchy but not yet on screen may not have collection item
+            // info populated. Use a parent with collection info as an indicator in those cases.
+            if (result.getCollectionItemInfo() != null || hasCollectionAsParent(result)) {
                 return result;
             }
             result = result.getParent();
@@ -565,6 +567,10 @@
         return null;
     }
 
+    private static boolean hasCollectionAsParent(AccessibilityNodeInfo node) {
+        return node.getParent() != null && node.getParent().getCollectionInfo() != null;
+    }
+
     private static AccessibilityNodeInfo findSwitch(AccessibilityNodeInfo root) throws Exception {
         if (Switch.class.getName().equals(root.getClassName().toString())) {
             return root;
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserAdminHelper.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserAdminHelper.java
index e092888..f20edcc 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserAdminHelper.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserAdminHelper.java
@@ -41,8 +41,8 @@
         ComponentName cn = PrimaryUserDeviceAdmin.ADMIN_RECEIVER_COMPONENT;
         if (mDpm.isAdminActive(cn)) {
             mDpm.removeActiveAdmin(cn);
-            // Wait until device admin is not active (with 2 minutes timeout).
-            for (int i = 0; i < 2 * 60 && mDpm.isAdminActive(cn); i++) {
+            // Wait until device admin is not active (with 5 minutes timeout).
+            for (int i = 0; i < 5 * 60 && mDpm.isAdminActive(cn); i++) {
                 Thread.sleep(1000);  // 1 second.
             }
         }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index 599a31c..c3962fb 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -22,6 +22,9 @@
 
 import android.util.Log;
 
+import com.android.compatibility.common.util.CddTest;
+
+@CddTest(requirement="7.4.7/C-1-1,H-1-1,C-2-1")
 public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
 
     private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
diff --git a/hostsidetests/numberblocking/app/Android.mk b/hostsidetests/numberblocking/app/Android.mk
index 08bf132..a84d11c 100644
--- a/hostsidetests/numberblocking/app/Android.mk
+++ b/hostsidetests/numberblocking/app/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt androidx.test.rules
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt androidx.test.rules compatibility-device-util
 
 LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
diff --git a/hostsidetests/numberblocking/app/src/com/android/cts/numberblocking/hostside/CallBlockingTest.java b/hostsidetests/numberblocking/app/src/com/android/cts/numberblocking/hostside/CallBlockingTest.java
index 674dab2..c23ba84 100644
--- a/hostsidetests/numberblocking/app/src/com/android/cts/numberblocking/hostside/CallBlockingTest.java
+++ b/hostsidetests/numberblocking/app/src/com/android/cts/numberblocking/hostside/CallBlockingTest.java
@@ -29,6 +29,8 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -36,6 +38,7 @@
 /**
  * Tests call blocking in a multi-user environment.
  */
+@CddTest(requirement="7.4.1.1/C-1-1")
 public class CallBlockingTest extends BaseNumberBlockingClientTest {
     private static final String QUERY_CALL_THROUGH_OUR_CONNECTION_SERVICE = CallLog.Calls.NUMBER
             + " = ? AND " + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + " = ?";
@@ -65,6 +68,7 @@
         assertNull(mTelecomManager.getPhoneAccount(getPhoneAccountHandle()));
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-3,C-1-4")
     public void testIncomingCallFromBlockedNumberIsRejected() throws Exception {
         // Make sure no lingering values from previous runs.
         cleanupCall(false /* verifyNoCallLogsWritten */);
diff --git a/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java b/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
index 1718ab7..6d5c336 100644
--- a/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
+++ b/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
@@ -119,7 +119,7 @@
             runTestAsPrimaryUser(CALL_BLOCKING_TEST_CLASS_NAME, "testUnregisterPhoneAccount");
 
             // Run tests as secondary user.
-            assertTrue(getDevice().startUser(mSecondaryUserId));
+            startUserAndWait(mSecondaryUserId);
 
             // Ensure that a privileged app cannot block numbers when the current user is a
             // secondary user.
@@ -142,6 +142,29 @@
         }
     }
 
+    /** Starts user {@code userId} and waits until it is in state RUNNING_UNLOCKED. */
+    protected void startUserAndWait(int userId) throws Exception {
+        getDevice().startUser(userId);
+
+        final String desiredState = "RUNNING_UNLOCKED";
+        final long USER_STATE_TIMEOUT_MS = 60_0000; // 1 minute
+        final long timeout = System.currentTimeMillis() + USER_STATE_TIMEOUT_MS;
+        final String command = String.format("am get-started-user-state %d", userId);
+        String output = "";
+        while (System.currentTimeMillis() <= timeout) {
+            output = getDevice().executeShellCommand(command);
+            if (output.contains(desiredState)) {
+                return;
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                // Do nothing.
+            }
+        }
+        fail("User state of " + userId + " was '" + output + "' rather than " + desiredState);
+    }
+
     private void createSecondaryUser() throws Exception {
         mSecondaryUserId = getDevice().createUser(SECONDARY_USER_NAME);
         getDevice().waitForDeviceAvailable();
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
index 02bc735..657c0a6 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
@@ -19,6 +19,7 @@
 import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
 
 import android.os.BatteryStatsProto;
+import android.service.battery.BatteryServiceDumpProto;
 import android.service.batterystats.BatteryStatsServiceDumpProto;
 import android.view.DisplayStateEnum;
 
@@ -70,6 +71,7 @@
 
     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 REMOVE_CONFIG_CMD = "cmd stats config remove";
     public static final String CONFIG_UID = "1000";
@@ -502,6 +504,14 @@
     }
 
     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");
     }
 
@@ -590,7 +600,7 @@
     }
 
     protected void turnBatterySaverOn() throws Exception {
-        getDevice().executeShellCommand("cmd battery unplug");
+        unplugDevice();
         getDevice().executeShellCommand("settings put global low_power 1");
     }
 
@@ -655,6 +665,21 @@
     }
 
     /**
+     * Determines if the device has a battery.
+     */
+    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);
+        }
+    }
+
+    /**
      * Determines if the device has |file|.
      */
     protected boolean doesFileExist(String file) throws Exception {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
index cc3b47c..34adcd1 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
@@ -338,6 +338,7 @@
             return;
         }
         if (!hasFeature(FEATURE_WATCH, false)) return;
+        if (!hasBattery()) return;
         StatsdConfig.Builder config = getPulledConfig();
         FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
             .setField(Atom.REMAINING_BATTERY_CAPACITY_FIELD_NUMBER)
@@ -365,6 +366,7 @@
             return;
         }
         if (!hasFeature(FEATURE_WATCH, false)) return;
+        if (!hasBattery()) return;
         StatsdConfig.Builder config = getPulledConfig();
         FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
                 .setField(Atom.FULL_BATTERY_CAPACITY_FIELD_NUMBER)
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index 01b5d88..9df2d5c 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -36,6 +36,8 @@
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.RunUtil;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.regex.Matcher;
@@ -129,6 +131,7 @@
      * Check if adb serial number, USB serial number, ro.serialno, and android.os.Build.SERIAL
      * all matches and meets the format requirement [a-zA-Z0-9]{6,20}
      */
+    @CddTest(requirement="7.7.1/C-1-2")
     @AppModeFull(reason = "serial can not be read by instant apps")
     public void testUsbSerialReadOnDeviceMatches() throws Exception {
         installApp(false);
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index 8d8adb1..8838492 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -299,9 +299,12 @@
 
     private void setBatteryCharging(final boolean charging) throws Exception {
         final BatteryManager bm = mContext.getSystemService(BatteryManager.class);
-        final String cmd = "dumpsys battery " + (charging ? "reset" : "unplug");
-        executeAndLog(cmd);
-        if (!charging) {
+        if (charging) {
+            executeAndLog("dumpsys battery reset");
+        } else {
+            executeAndLog("dumpsys battery unplug");
+            executeAndLog("dumpsys battery set status " +
+                    BatteryManager.BATTERY_STATUS_DISCHARGING);
             assertTrue("Battery could not be unplugged", waitUntil(() -> !bm.isCharging(), 5_000));
         }
     }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
index dd855da..cf9ec4c 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
@@ -92,6 +92,16 @@
         }
     }
 
+    boolean hasBattery() throws Exception {
+        Intent batteryInfo = getContext().registerReceiver(
+                null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        boolean present = batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
+        if (!present) {
+            Log.i(TAG, "Device doesn't have a battery.");
+        }
+        return present;
+    }
+
     void setBatteryState(boolean plugged, int level) throws Exception {
         if (plugged) {
             SystemUtil.runShellCommand(getInstrumentation(), "cmd battery set ac 1");
@@ -210,6 +220,11 @@
      * not plugged in but has sufficient power.
      */
     public void testBatteryNotLowConstraintExecutes_withoutPower() throws Exception {
+        // "Without power" test case is valid only for devices with a battery.
+        if (!hasBattery()) {
+            return;
+        }
+
         setBatteryState(false, 100);
         waitFor(2_000);
         verifyChargingState(false);
@@ -234,6 +249,11 @@
      * the device is not on power.
      */
     public void testChargingConstraintFails() throws Exception {
+        // "Without power" test case is valid only for devices with a battery.
+        if (!hasBattery()) {
+            return;
+        }
+
         setBatteryState(false, 100);
         verifyChargingState(false);
 
@@ -273,6 +293,11 @@
      * the battery level is critical and not on power.
      */
     public void testBatteryNotLowConstraintFails_withoutPower() throws Exception {
+        // "Without power" test case is valid only for devices with a battery.
+        if (!hasBattery()) {
+            return;
+        }
+
         setBatteryState(false, mLowBatteryWarningLevel);
         // setBatteryState() waited for the charging/not-charging state to formally settle,
         // but battery level reporting lags behind that.  wait a moment to let that happen
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index d535fab..e16c8e8 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -67,11 +67,14 @@
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 
+import com.android.compatibility.common.util.CddTest;
+
 /**
  * This class performs end-to-end testing of the accessibility feature by
  * creating an {@link Activity} and poking around so {@link AccessibilityEvent}s
  * are generated and their correct dispatch verified.
  */
+@CddTest(requirement="3.10/C-1-2,W-1-1")
 public class AccessibilityEndToEndTest extends
         AccessibilityActivityTestCase<AccessibilityEndToEndActivity> {
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
index f01251a..c6ec02e 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
@@ -27,10 +27,13 @@
 
 import java.util.List;
 
+import com.android.compatibility.common.util.CddTest;
+
 /**
  * This test case is responsible to verify that the intent for launching
  * accessibility settings has an activity that handles it.
  */
+@CddTest(requirement="3.10/C-1-3")
 @Presubmit
 public class AccessibilitySettingsTest extends AndroidTestCase {
 
diff --git a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index 5beb1c4..4ce31a3 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -49,6 +49,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.SystemUtil;
 
 public class ActivityManagerProcessStateTest extends InstrumentationTestCase {
@@ -527,6 +528,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.
      */
+    @CddTest(requirement="3.5/C-0-7")
     public void testBackgroundCheckStopsService() throws Exception {
         final Parcel data = Parcel.obtain();
         ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent,
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 6c25a2dc..0b4f4fb 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -44,11 +44,13 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.IBinderParcelable;
 import com.android.compatibility.common.util.SystemUtil;
 
 import java.util.List;
 
+@CddTest(requirement="3.5/C-0-2")
 public class ServiceTest extends ActivityTestsBase {
     private static final String TAG = "ServiceTest";
     private static final String NOTIFICATION_CHANNEL_ID = TAG;
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 6d22063..6dc1df7 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -43,6 +43,7 @@
 import android.telephony.TelephonyManager;
 import android.test.InstrumentationTestCase;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.PropertyUtil;
 
 import java.lang.reflect.Field;
@@ -293,6 +294,7 @@
         }
     }
 
+    @CddTest(requirement="7.4.4/C-1-1,C-2-1")
     public void testNfcFeatures() {
         if (NfcAdapter.getDefaultAdapter(mContext) != null) {
             // Watches MAY support all FEATURE_NFC features when an NfcAdapter is available, but
@@ -501,6 +503,7 @@
         // TODO: Add tests for the other touchscreen features.
     }
 
+    @CddTest(requirement="7.7.2/C-2-1")
     public void testUsbAccessory() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) &&
                 !mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) &&
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 428403d..802b081 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -29,8 +29,7 @@
 
         <uses-library android:name="android.test.runner" />
 
-        <activity android:name=".LoginActivity"
-                  android:screenOrientation="portrait">
+        <activity android:name=".LoginActivity" >
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it maks easier to launch
                      this app during CTS development... -->
@@ -141,6 +140,24 @@
                 <action android:name="android.service.autofill.AutofillService" />
             </intent-filter>
         </service>
+
+        <!-- Mock IME -->
+        <service
+            android:name="com.android.cts.mockime.MockIme"
+            android:label="Mock IME"
+            android:permission="android.permission.BIND_INPUT_METHOD">
+            <intent-filter>
+                <action android:name="android.view.InputMethod" />
+            </intent-filter>
+            <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">
+        </provider>
+
     </application>
 
     <instrumentation
diff --git a/tests/autofillservice/res/xml/method.xml b/tests/autofillservice/res/xml/method.xml
new file mode 100644
index 0000000..7f8b13a
--- /dev/null
+++ b/tests/autofillservice/res/xml/method.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android">
+</input-method>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 7eb8bcf..8ea2c14 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -35,6 +35,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.RequiredFeatureRule;
+import com.android.cts.mockime.MockImeSessionRule;
 
 import org.junit.After;
 import org.junit.Before;
@@ -76,6 +77,10 @@
         }
     };
 
+    @ClassRule
+    public static final MockImeSessionRule sMockImeSessionRule =
+            new MockImeSessionRule(/* ignoreInitException= */ true);
+
     @Rule
     public final RetryRule mRetryRule = new RetryRule(2);
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
index f5a4f89..02b2e13 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
@@ -71,28 +71,13 @@
     public final void testTapLink_changeOrientationThenTapBack() throws Exception {
         assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
 
-        final int width = mUiBot.getDevice().getDisplayWidth();
-        final int heigth = mUiBot.getDevice().getDisplayHeight();
-        final int min = Math.min(width, heigth);
-
-        assumeTrue("Screen size is too small (" + width + "x" + heigth + ")", min >= 500);
-        Log.d(TAG, "testTapLink_changeOrientationThenTapBack(): screen size is "
-                + width + "x" + heigth);
-
         mUiBot.setScreenOrientation(UiBot.PORTRAIT);
         try {
-            runShellCommand("wm size 1080x1920");
-            runShellCommand("wm density 320");
             saveUiRestoredAfterTappingLinkTest(
                     PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
         } finally {
             mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-            try {
-                cleanUpAfterScreenOrientationIsBackToPortrait();
-            } finally {
-                runShellCommand("wm density reset");
-                runShellCommand("wm size reset");
-            }
+            cleanUpAfterScreenOrientationIsBackToPortrait();
         }
     }
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
index 81b3fa2..87b0ff7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
@@ -19,9 +19,21 @@
 import static android.autofillservice.cts.Helper.ID_USERNAME;
 import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
 
+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 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.view.View;
+
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.MockImeSession;
 
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -31,6 +43,8 @@
 
 public class DatasetFilteringTest extends AbstractLoginActivityTestCase {
 
+    private static final long MOCK_IME_TIMEOUT_MS = 5_000;
+
     private static String sMaxDatasets;
 
     @BeforeClass
@@ -108,7 +122,7 @@
     }
 
     @Test
-    public void testFilter_usingKeyboard() throws Exception {
+    public void testFilter_ejectingEvents() throws Exception {
         final String aa = "Two A's";
         final String ab = "A and B";
         final String b = "Only B";
@@ -165,6 +179,74 @@
 
     @Test
     @AppModeFull // testFilter() is enough to test ephemeral apps support
+    public void testFilter_usingKeyboard() throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+
+        enableService();
+
+        // Set expectations.
+        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());
+
+        final ImeEventStream stream = mockImeSession.openEventStream();
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // 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'
+        sendKeyEvents("KEYCODE_DEL"); // TODO: add new method on MockIme for it
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        sendKeyEvents("KEYCODE_DEL"); // TODO: add new method on MockIme for it
+        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 // testFilter() is enough to test ephemeral apps support
     public void testFilter_nullValuesAlwaysMatched() throws Exception {
         final String aa = "Two A's";
         final String ab = "A and B";
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index 2037259..a9ba097 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -802,7 +802,20 @@
      * Checks if screen orientation can be changed.
      */
     public static boolean isRotationSupported(Context context) {
-        return !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+        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;
     }
 
     /**
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index c476c7c..d52bcf6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -86,6 +86,10 @@
 import android.view.autofill.AutofillManager;
 import android.widget.RemoteViews;
 
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.MockImeSession;
+
 import org.junit.Test;
 
 import java.util.concurrent.CountDownLatch;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 90358ab..1fe998e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -93,8 +93,6 @@
             "autofill_picker_accessibility_title";
     private static final String RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE =
             "autofill_save_accessibility_title";
-    private static final String RESOURCE_BOOLEAN_CONFIG_FORCE_DEFAULT_ORIENTATION =
-            "config_forceDefaultOrientation";
 
 
     static final BySelector DATASET_PICKER_SELECTOR = By.res("android", RESOURCE_ID_DATASET_PICKER);
@@ -874,18 +872,4 @@
         final int booleanId = resources.getIdentifier(id, "bool", "android");
         return resources.getBoolean(booleanId);
     }
-
-    /**
-     * Returns {@code true} if display rotation is supported, {@code false} otherwise.
-     */
-    public boolean isScreenRotationSupported() {
-        try {
-            return !getBoolean(RESOURCE_BOOLEAN_CONFIG_FORCE_DEFAULT_ORIENTATION);
-        } catch (Resources.NotFoundException e) {
-            Log.d(TAG, "Resource not found: "
-                    + RESOURCE_BOOLEAN_CONFIG_FORCE_DEFAULT_ORIENTATION
-                    + ". Assume rotation supported");
-            return true;
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/ImeCommand.java b/tests/autofillservice/src/com/android/cts/mockime/ImeCommand.java
new file mode 100644
index 0000000..ad81eb5
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/ImeCommand.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mockime;
+
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+
+public final class ImeCommand {
+
+    private static final String NAME_KEY = "name";
+    private static final String ID_KEY = "id";
+    private static final String DISPATCH_TO_MAIN_THREAD_KEY = "dispatchToMainThread";
+    private static final String EXTRA_KEY = "extra";
+
+    @NonNull
+    private final String mName;
+    private final long mId;
+    private final boolean mDispatchToMainThread;
+    @NonNull
+    private final Bundle mExtras;
+
+    ImeCommand(@NonNull String name, long id, boolean dispatchToMainThread,
+            @NonNull Bundle extras) {
+        mName = name;
+        mId = id;
+        mDispatchToMainThread = dispatchToMainThread;
+        mExtras = extras;
+    }
+
+    private ImeCommand(@NonNull Bundle bundle) {
+        mName = bundle.getString(NAME_KEY);
+        mId = bundle.getLong(ID_KEY);
+        mDispatchToMainThread = bundle.getBoolean(DISPATCH_TO_MAIN_THREAD_KEY);
+        mExtras = bundle.getParcelable(EXTRA_KEY);
+    }
+
+    static ImeCommand fromBundle(@NonNull Bundle bundle) {
+        return new ImeCommand(bundle);
+    }
+
+    Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putString(NAME_KEY, mName);
+        bundle.putLong(ID_KEY, mId);
+        bundle.putBoolean(DISPATCH_TO_MAIN_THREAD_KEY, mDispatchToMainThread);
+        bundle.putParcelable(EXTRA_KEY, mExtras);
+        return bundle;
+    }
+
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    public long getId() {
+        return mId;
+    }
+
+    public boolean shouldDispatchToMainThread() {
+        return mDispatchToMainThread;
+    }
+
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/ImeEvent.java b/tests/autofillservice/src/com/android/cts/mockime/ImeEvent.java
new file mode 100644
index 0000000..d4090fb
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/ImeEvent.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mockime;
+
+import android.inputmethodservice.AbstractInputMethodService;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.view.View;
+
+/**
+ * An immutable object that stores event happened in the {@link MockIme}.
+ */
+public final class ImeEvent {
+
+    private enum ReturnType {
+        Null,
+        KnownUnsupportedType,
+        Boolean,
+    }
+
+    private static ReturnType getReturnTypeFromObject(@Nullable Object object) {
+        if (object == null) {
+            return ReturnType.Null;
+        }
+        if (object instanceof AbstractInputMethodService.AbstractInputMethodImpl) {
+            return ReturnType.KnownUnsupportedType;
+        }
+        if (object instanceof View) {
+            return ReturnType.KnownUnsupportedType;
+        }
+        if (object instanceof Boolean) {
+            return ReturnType.Boolean;
+        }
+        throw new UnsupportedOperationException("Unsupported return type=" + object);
+    }
+
+    ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName, int threadId,
+            boolean isMainThread, long enterTimestamp, long exitTimestamp, long enterWallTime,
+            long exitWallTime, @NonNull ImeState enterState, @Nullable ImeState exitState,
+            @NonNull Bundle arguments, @Nullable Object returnValue) {
+        this(eventName, nestLevel, threadName, threadId, isMainThread, enterTimestamp,
+                exitTimestamp, enterWallTime, exitWallTime, enterState, exitState, arguments,
+                returnValue, getReturnTypeFromObject(returnValue));
+    }
+
+    private ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName,
+            int threadId, boolean isMainThread, long enterTimestamp, long exitTimestamp,
+            long enterWallTime, long exitWallTime, @NonNull ImeState enterState,
+            @Nullable ImeState exitState, @NonNull Bundle arguments, @Nullable Object returnValue,
+            @NonNull ReturnType returnType) {
+        mEventName = eventName;
+        mNestLevel = nestLevel;
+        mThreadName = threadName;
+        mThreadId = threadId;
+        mIsMainThread = isMainThread;
+        mEnterTimestamp = enterTimestamp;
+        mExitTimestamp = exitTimestamp;
+        mEnterWallTime = enterWallTime;
+        mExitWallTime = exitWallTime;
+        mEnterState = enterState;
+        mExitState = exitState;
+        mArguments = arguments;
+        mReturnValue = returnValue;
+        mReturnType = returnType;
+    }
+
+    @NonNull
+    Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putString("mEventName", mEventName);
+        bundle.putInt("mNestLevel", mNestLevel);
+        bundle.putString("mThreadName", mThreadName);
+        bundle.putInt("mThreadId", mThreadId);
+        bundle.putBoolean("mIsMainThread", mIsMainThread);
+        bundle.putLong("mEnterTimestamp", mEnterTimestamp);
+        bundle.putLong("mExitTimestamp", mExitTimestamp);
+        bundle.putLong("mEnterWallTime", mEnterWallTime);
+        bundle.putLong("mExitWallTime", mExitWallTime);
+        bundle.putBundle("mEnterState", mEnterState.toBundle());
+        bundle.putBundle("mExitState", mExitState != null ? mExitState.toBundle() : null);
+        bundle.putBundle("mArguments", mArguments);
+        bundle.putString("mReturnType", mReturnType.name());
+        switch (mReturnType) {
+            case Null:
+            case KnownUnsupportedType:
+                break;
+            case Boolean:
+                bundle.putBoolean("mReturnValue", getReturnBooleanValue());
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported type=" + mReturnType);
+        }
+        return bundle;
+    }
+
+    @NonNull
+    static ImeEvent fromBundle(@NonNull Bundle bundle) {
+        final String eventName = bundle.getString("mEventName");
+        final int nestLevel = bundle.getInt("mNestLevel");
+        final String threadName = bundle.getString("mThreadName");
+        final int threadId = bundle.getInt("mThreadId");
+        final boolean isMainThread = bundle.getBoolean("mIsMainThread");
+        final long enterTimestamp = bundle.getLong("mEnterTimestamp");
+        final long exitTimestamp = bundle.getLong("mExitTimestamp");
+        final long enterWallTime = bundle.getLong("mEnterWallTime");
+        final long exitWallTime = bundle.getLong("mExitWallTime");
+        final ImeState enterState = ImeState.fromBundle(bundle.getBundle("mEnterState"));
+        final ImeState exitState = ImeState.fromBundle(bundle.getBundle("mExitState"));
+        final Bundle arguments = bundle.getBundle("mArguments");
+        final Object result;
+        final ReturnType returnType = ReturnType.valueOf(bundle.getString("mReturnType"));
+        switch (returnType) {
+            case Null:
+            case KnownUnsupportedType:
+                result = null;
+                break;
+            case Boolean:
+                result = bundle.getBoolean("mReturnValue");
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported type=" + returnType);
+        }
+        return new ImeEvent(eventName, nestLevel, threadName,
+                threadId, isMainThread, enterTimestamp, exitTimestamp, enterWallTime, exitWallTime,
+                enterState, exitState, arguments, result, returnType);
+    }
+
+    /**
+     * Returns a string that represents the type of this event.
+     *
+     * <p>Examples: &quot;onCreate&quot;, &quot;onStartInput&quot;, ...</p>
+     *
+     * <p>TODO: Use enum type or something like that instead of raw String type.</p>
+     * @return A string that represents the type of this event.
+     */
+    @NonNull
+    public String getEventName() {
+        return mEventName;
+    }
+
+    /**
+     * Returns the nest level of this event.
+     *
+     * <p>For instance, when &quot;showSoftInput&quot; internally calls
+     * &quot;onStartInputView&quot;, the event for &quot;onStartInputView&quot; has 1 level higher
+     * nest level than &quot;showSoftInput&quot;.</p>
+     */
+    public int getNestLevel() {
+        return mNestLevel;
+    }
+
+    /**
+     * @return Name of the thread, where the event was consumed.
+     */
+    @NonNull
+    public String getThreadName() {
+        return mThreadName;
+    }
+
+    /**
+     * @return Thread ID (TID) of the thread, where the event was consumed.
+     */
+    public int getThreadId() {
+        return mThreadId;
+    }
+
+    /**
+     * @return {@code true} if the event was being consumed in the main thread.
+     */
+    public boolean isMainThread() {
+        return mIsMainThread;
+    }
+
+    /**
+     * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
+     *         the corresponding event handler was called back.
+     */
+    public long getEnterTimestamp() {
+        return mEnterTimestamp;
+    }
+
+    /**
+     * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
+     *         the corresponding event handler finished.
+     */
+    public long getExitTimestamp() {
+        return mExitTimestamp;
+    }
+
+    /**
+     * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
+     *         event handler was called back.
+     */
+    public long getEnterWallTime() {
+        return mEnterWallTime;
+    }
+
+    /**
+     * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
+     *         event handler finished.
+     */
+    public long getExitWallTime() {
+        return mExitWallTime;
+    }
+
+    /**
+     * @return IME state snapshot taken when the corresponding event handler was called back.
+     */
+    @NonNull
+    public ImeState getEnterState() {
+        return mEnterState;
+    }
+
+    /**
+     * @return IME state snapshot taken when the corresponding event handler finished.
+     */
+    @Nullable
+    public ImeState getExitState() {
+        return mExitState;
+    }
+
+    /**
+     * @return {@link Bundle} that stores parameters passed to the corresponding event handler.
+     */
+    @NonNull
+    public Bundle getArguments() {
+        return mArguments;
+    }
+
+    /**
+     * @return result value of this event.
+     * @throws NullPointerException if the return value is {@code null}
+     * @throws ClassCastException if the return value is non-{@code null} object that is different
+     *                            from {@link Boolean}
+     */
+    public boolean getReturnBooleanValue() {
+        if (mReturnType == ReturnType.Null) {
+            throw new NullPointerException();
+        }
+        if (mReturnType != ReturnType.Boolean) {
+            throw new ClassCastException();
+        }
+        return (Boolean) mReturnValue;
+    }
+
+    /**
+     * @return {@code true} if the event is issued when the event starts, not when the event
+     * finishes.
+     */
+    public boolean isEnterEvent() {
+        return mExitState == null;
+    }
+
+    @NonNull
+    private final String mEventName;
+    private final int mNestLevel;
+    @NonNull
+    private final String mThreadName;
+    private final int mThreadId;
+    private final boolean mIsMainThread;
+    private final long mEnterTimestamp;
+    private final long mExitTimestamp;
+    private final long mEnterWallTime;
+    private final long mExitWallTime;
+    @NonNull
+    private final ImeState mEnterState;
+    @Nullable
+    private final ImeState mExitState;
+    @NonNull
+    private final Bundle mArguments;
+    @Nullable
+    private final Object mReturnValue;
+    @NonNull
+    private final ReturnType mReturnType;
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/ImeEventStream.java b/tests/autofillservice/src/com/android/cts/mockime/ImeEventStream.java
new file mode 100644
index 0000000..d866da0
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/ImeEventStream.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mockime;
+
+import android.os.Bundle;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import android.view.inputmethod.EditorInfo;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * A utility class that provides basic query operations and wait primitives for a series of
+ * {@link ImeEvent} sent from the {@link MockIme}.
+ *
+ * <p>All public methods are not thread-safe.</p>
+ */
+public final class ImeEventStream {
+
+    private static final String LONG_LONG_SPACES = "                                        ";
+
+    private static DateTimeFormatter sSimpleDateTimeFormatter =
+            DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
+    @NonNull
+    private final Supplier<ImeEventArray> mEventSupplier;
+    private int mCurrentPosition;
+
+    ImeEventStream(@NonNull Supplier<ImeEventArray> supplier) {
+        this(supplier, 0 /* position */);
+    }
+
+    private ImeEventStream(@NonNull Supplier<ImeEventArray> supplier, int position) {
+        mEventSupplier = supplier;
+        mCurrentPosition = position;
+    }
+
+    /**
+     * Create a copy that starts from the same event position of this stream. Once a copy is created
+     * further event position change on this stream will not affect the copy.
+     *
+     * @return A new copy of this stream
+     */
+    public ImeEventStream copy() {
+        return new ImeEventStream(mEventSupplier, mCurrentPosition);
+    }
+
+    /**
+     * Advances the current event position by skipping events.
+     *
+     * @param length number of events to be skipped
+     * @throws IllegalArgumentException {@code length} is negative
+     */
+    public void skip(@IntRange(from = 0) int length) {
+        if (length < 0) {
+            throw new IllegalArgumentException("length cannot be negative: " + length);
+        }
+        mCurrentPosition += length;
+    }
+
+    /**
+     * Advances the current event position to the next to the last position.
+     */
+    public void skipAll() {
+        mCurrentPosition = mEventSupplier.get().mLength;
+    }
+
+    /**
+     * Find the first event that matches the given condition from the current position.
+     *
+     * <p>If there is such an event, this method returns such an event without moving the current
+     * event position.</p>
+     *
+     * <p>If there is such an event, this method returns {@link Optional#empty()} without moving the
+     * current event position.</p>
+     *
+     * @param condition the event condition to be matched
+     * @return {@link Optional#empty()} if there is no such an event. Otherwise the matched event is
+     *         returned
+     */
+    @NonNull
+    public Optional<ImeEvent> findFirst(Predicate<ImeEvent> condition) {
+        final ImeEventArray latest = mEventSupplier.get();
+        int index = mCurrentPosition;
+        while (true) {
+            if (index >= latest.mLength) {
+                return Optional.empty();
+            }
+            if (condition.test(latest.mArray[index])) {
+                return Optional.of(latest.mArray[index]);
+            }
+            ++index;
+        }
+    }
+
+    /**
+     * Find the first event that matches the given condition from the current position.
+     *
+     * <p>If there is such an event, this method returns such an event and set the current event
+     * position to that event.</p>
+     *
+     * <p>If there is such an event, this method returns {@link Optional#empty()} without moving the
+     * current event position.</p>
+     *
+     * @param condition the event condition to be matched
+     * @return {@link Optional#empty()} if there is no such an event. Otherwise the matched event is
+     *         returned
+     */
+    @NonNull
+    public Optional<ImeEvent> seekToFirst(Predicate<ImeEvent> condition) {
+        final ImeEventArray latest = mEventSupplier.get();
+        while (true) {
+            if (mCurrentPosition >= latest.mLength) {
+                return Optional.empty();
+            }
+            if (condition.test(latest.mArray[mCurrentPosition])) {
+                return Optional.of(latest.mArray[mCurrentPosition]);
+            }
+            ++mCurrentPosition;
+        }
+    }
+
+    private static void dumpEvent(@NonNull StringBuilder sb, @NonNull ImeEvent event,
+            boolean fused) {
+        final String indentation = getWhiteSpaces(event.getNestLevel() * 2 + 2);
+        final long wallTime =
+                fused ? event.getEnterWallTime() :
+                        event.isEnterEvent() ? event.getEnterWallTime() : event.getExitWallTime();
+        sb.append(sSimpleDateTimeFormatter.format(Instant.ofEpochMilli(wallTime)))
+                .append("  ")
+                .append(String.format("%5d", event.getThreadId()))
+                .append(indentation);
+        sb.append(fused ? "" : event.isEnterEvent() ? "[" : "]");
+        if (fused || event.isEnterEvent()) {
+            sb.append(event.getEventName())
+                    .append(':')
+                    .append(" args=");
+            dumpBundle(sb, event.getArguments());
+        }
+        sb.append('\n');
+    }
+
+    /**
+     * @return Debug info as a {@link String}.
+     */
+    public String dump() {
+        final ImeEventArray latest = mEventSupplier.get();
+        final StringBuilder sb = new StringBuilder();
+        sb.append("ImeEventStream:\n");
+        sb.append("  latest: array[").append(latest.mArray.length).append("] + {\n");
+        for (int i = 0; i < latest.mLength; ++i) {
+            // To compress the dump message, if the current event is an enter event and the next
+            // one is a corresponding exit event, we unify the output.
+            final boolean fused = areEnterExitPairedMessages(latest, i);
+            if (i == mCurrentPosition || (fused && ((i + 1) == mCurrentPosition))) {
+                sb.append("  ======== CurrentPosition ========  \n");
+            }
+            dumpEvent(sb, latest.mArray[fused ? ++i : i], fused);
+        }
+        if (mCurrentPosition >= latest.mLength) {
+            sb.append("  ======== CurrentPosition ========  \n");
+        }
+        sb.append("}\n");
+        return sb.toString();
+    }
+
+    /**
+     * @param array event array to be checked
+     * @param i index to be checked
+     * @return {@code true} if {@code array.mArray[i]} and {@code array.mArray[i + 1]} are two
+     *         paired events.
+     */
+    private static boolean areEnterExitPairedMessages(@NonNull ImeEventArray array,
+            @IntRange(from = 0) int i) {
+        return array.mArray[i] != null
+                && array.mArray[i].isEnterEvent()
+                && (i + 1) < array.mLength
+                && array.mArray[i + 1] != null
+                && array.mArray[i].getEventName().equals(array.mArray[i + 1].getEventName())
+                && array.mArray[i].getEnterTimestamp() == array.mArray[i + 1].getEnterTimestamp();
+    }
+
+    /**
+     * @param length length of the requested white space string
+     * @return {@link String} object whose length is {@code length}
+     */
+    private static String getWhiteSpaces(@IntRange(from = 0) final int length) {
+        if (length < LONG_LONG_SPACES.length()) {
+            return LONG_LONG_SPACES.substring(0, length);
+        }
+        final char[] indentationChars = new char[length];
+        Arrays.fill(indentationChars, ' ');
+        return new String(indentationChars);
+    }
+
+    private static void dumpBundle(@NonNull StringBuilder sb, @NonNull Bundle bundle) {
+        sb.append('{');
+        boolean first = true;
+        for (String key : bundle.keySet()) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(' ');
+            }
+            final Object object = bundle.get(key);
+            sb.append(key);
+            sb.append('=');
+            if (object instanceof EditorInfo) {
+                final EditorInfo info = (EditorInfo) object;
+                sb.append("EditorInfo{packageName=").append(info.packageName);
+                sb.append(" fieldId=").append(info.fieldId);
+                sb.append(" hintText=").append(info.hintText);
+                sb.append(" privateImeOptions=").append(info.privateImeOptions);
+                sb.append("}");
+            } else {
+                sb.append(object);
+            }
+        }
+        sb.append('}');
+    }
+
+    static class ImeEventArray {
+        @NonNull
+        public final ImeEvent[] mArray;
+        public final int mLength;
+        ImeEventArray(ImeEvent[] array, int length) {
+            mArray = array;
+            mLength = length;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/autofillservice/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
new file mode 100644
index 0000000..8497268
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mockime;
+
+import android.os.SystemClock;
+import androidx.annotation.NonNull;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+
+import java.util.Optional;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
+
+/**
+ * A set of utility methods to avoid boilerplate code when writing end-to-end tests.
+ */
+public final class ImeEventStreamTestUtils {
+    private static final long TIME_SLICE = 50;  // msec
+
+    /**
+     * Cannot be instantiated
+     */
+    private ImeEventStreamTestUtils() {}
+
+    /**
+     * Behavior mode of {@link #expectEvent(ImeEventStream, Predicate, EventFilterMode, long)}
+     */
+    public enum EventFilterMode {
+        /**
+         * All {@link ImeEvent} events should be checked
+         */
+        CHECK_ALL,
+        /**
+         * Only events that return {@code true} from {@link ImeEvent#isEnterEvent()} should be
+         * checked
+         */
+        CHECK_ENTER_EVENT_ONLY,
+        /**
+         * Only events that return {@code false} from {@link ImeEvent#isEnterEvent()} should be
+         * checked
+         */
+        CHECK_EXIT_EVENT_ONLY,
+    }
+
+    /**
+     * Wait until an event that matches the given {@code condition} is found in the stream.
+     *
+     * <p>When this method succeeds to find an event that matches the given {@code condition}, the
+     * stream position will be set to the next to the found object then the event found is returned.
+     * </p>
+     *
+     * <p>For convenience, this method automatically filter out exit events (events that return
+     * {@code false} from {@link ImeEvent#isEnterEvent()}.</p>
+     *
+     * <p>TODO: Consider renaming this to {@code expectEventEnter} or something like that.</p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param timeout timeout in millisecond
+     * @return {@link ImeEvent} found
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    @NonNull
+    public static ImeEvent expectEvent(@NonNull ImeEventStream stream,
+            @NonNull Predicate<ImeEvent> condition, long timeout) throws TimeoutException {
+        return expectEvent(stream, condition, EventFilterMode.CHECK_ENTER_EVENT_ONLY, timeout);
+    }
+
+    /**
+     * Wait until an event that matches the given {@code condition} is found in the stream.
+     *
+     * <p>When this method succeeds to find an event that matches the given {@code condition}, the
+     * stream position will be set to the next to the found object then the event found is returned.
+     * </p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param filterMode controls how events are filtered out
+     * @param timeout timeout in millisecond
+     * @return {@link ImeEvent} found
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    @NonNull
+    public static ImeEvent expectEvent(@NonNull ImeEventStream stream,
+            @NonNull Predicate<ImeEvent> condition, EventFilterMode filterMode, long timeout)
+            throws TimeoutException {
+        try {
+            Optional<ImeEvent> result;
+            while (true) {
+                if (timeout < 0) {
+                    throw new TimeoutException(
+                            "event not found within the timeout: " + stream.dump());
+                }
+                final Predicate<ImeEvent> combinedCondition;
+                switch (filterMode) {
+                    case CHECK_ALL:
+                        combinedCondition = condition;
+                        break;
+                    case CHECK_ENTER_EVENT_ONLY:
+                        combinedCondition = event -> event.isEnterEvent() && condition.test(event);
+                        break;
+                    case CHECK_EXIT_EVENT_ONLY:
+                        combinedCondition = event -> !event.isEnterEvent() && condition.test(event);
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unknown filterMode " + filterMode);
+                }
+                result = stream.seekToFirst(combinedCondition);
+                if (result.isPresent()) {
+                    break;
+                }
+                Thread.sleep(TIME_SLICE);
+                timeout -= TIME_SLICE;
+            }
+            final ImeEvent event = result.get();
+            if (event == null) {
+                throw new NullPointerException("found event is null: " + stream.dump());
+            }
+            stream.skip(1);
+            return event;
+        } catch (InterruptedException e) {
+            throw new RuntimeException("expectEvent failed: " + stream.dump(), e);
+        }
+    }
+
+    /**
+     * Checks if {@param eventName} has occurred on the EditText(or TextView) of the current
+     * activity.
+     * @param eventName event name to check
+     * @param marker Test marker set to {@link android.widget.EditText#setPrivateImeOptions(String)}
+     * @return true if event occurred.
+     */
+    public static Predicate<ImeEvent> editorMatcher(
+        @NonNull String eventName, @NonNull String marker) {
+        return event -> {
+            if (!TextUtils.equals(eventName, event.getEventName())) {
+                return false;
+            }
+            final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+            return TextUtils.equals(marker, editorInfo.privateImeOptions);
+        };
+    }
+
+    /**
+    * Checks if {@code eventName} has occurred on the EditText(or TextView) of the current
+    * activity.
+    * @param eventName event name to check
+    * @param fieldId typically same as {@link android.view.View#getId()}.
+    * @return true if event occurred.
+    */
+    public static Predicate<ImeEvent> editorMatcher(@NonNull String eventName, int fieldId) {
+        return event -> {
+            if (!TextUtils.equals(eventName, event.getEventName())) {
+                return false;
+            }
+            final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+            return fieldId == editorInfo.fieldId;
+        };
+    }
+
+    /**
+     * Wait until an event that matches the given command is consumed by the {@link MockIme}.
+     *
+     * <p>For convenience, this method automatically filter out enter events (events that return
+     * {@code true} from {@link ImeEvent#isEnterEvent()}.</p>
+     *
+     * <p>TODO: Consider renaming this to {@code expectCommandConsumed} or something like that.</p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param command {@link ImeCommand} to be waited for.
+     * @param timeout timeout in millisecond
+     * @return {@link ImeEvent} found
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    @NonNull
+    public static ImeEvent expectCommand(@NonNull ImeEventStream stream,
+            @NonNull ImeCommand command, long timeout) throws TimeoutException {
+        final Predicate<ImeEvent> predicate = event -> {
+            if (!TextUtils.equals("onHandleCommand", event.getEventName())) {
+                return false;
+            }
+            final ImeCommand eventCommand =
+                    ImeCommand.fromBundle(event.getArguments().getBundle("command"));
+            return eventCommand.getId() == command.getId();
+        };
+        return expectEvent(stream, predicate, EventFilterMode.CHECK_EXIT_EVENT_ONLY, timeout);
+    }
+
+    /**
+     * Assert that an event that matches the given {@code condition} will no be found in the stream
+     * within the given {@code timeout}.
+     *
+     * <p>When this method succeeds, the stream position will not change.</p>
+     *
+     * <p>For convenience, this method automatically filter out exit events (events that return
+     * {@code false} from {@link ImeEvent#isEnterEvent()}.</p>
+     *
+     * <p>TODO: Consider renaming this to {@code notExpectEventEnter} or something like that.</p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param timeout timeout in millisecond
+     * @throws AssertionError if such an event is found within the given {@code timeout}
+     */
+    public static void notExpectEvent(@NonNull ImeEventStream stream,
+            @NonNull Predicate<ImeEvent> condition, long timeout) {
+        notExpectEvent(stream, condition, EventFilterMode.CHECK_ENTER_EVENT_ONLY, timeout);
+    }
+
+    /**
+     * Assert that an event that matches the given {@code condition} will no be found in the stream
+     * within the given {@code timeout}.
+     *
+     * <p>When this method succeeds, the stream position will not change.</p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param filterMode controls how events are filtered out
+     * @param timeout timeout in millisecond
+     * @throws AssertionError if such an event is found within the given {@code timeout}
+     */
+    public static void notExpectEvent(@NonNull ImeEventStream stream,
+            @NonNull Predicate<ImeEvent> condition, EventFilterMode filterMode, long timeout) {
+        final Predicate<ImeEvent> combinedCondition;
+        switch (filterMode) {
+            case CHECK_ALL:
+                combinedCondition = condition;
+                break;
+            case CHECK_ENTER_EVENT_ONLY:
+                combinedCondition = event -> event.isEnterEvent() && condition.test(event);
+                break;
+            case CHECK_EXIT_EVENT_ONLY:
+                combinedCondition = event -> !event.isEnterEvent() && condition.test(event);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown filterMode " + filterMode);
+        }
+        try {
+            while (true) {
+                if (timeout < 0) {
+                    return;
+                }
+                if (stream.findFirst(combinedCondition).isPresent()) {
+                    throw new AssertionError("notExpectEvent failed: " + stream.dump());
+                }
+                Thread.sleep(TIME_SLICE);
+                timeout -= TIME_SLICE;
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException("notExpectEvent failed: " + stream.dump(), e);
+        }
+    }
+
+    /**
+     * A specialized version of {@link #expectEvent(ImeEventStream, Predicate, long)} to wait for
+     * {@link android.view.inputmethod.InputMethod#bindInput(InputBinding)}.
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param targetProcessPid PID to be matched to {@link InputBinding#getPid()}
+     * @param timeout timeout in millisecond
+     * @throws TimeoutException when "bindInput" is not called within {@code timeout} msec
+     */
+    public static void expectBindInput(@NonNull ImeEventStream stream, int targetProcessPid,
+            long timeout) throws TimeoutException {
+        expectEvent(stream, event -> {
+            if (!TextUtils.equals("bindInput", event.getEventName())) {
+                return false;
+            }
+            final InputBinding binding = event.getArguments().getParcelable("binding");
+            return binding.getPid() == targetProcessPid;
+        }, EventFilterMode.CHECK_EXIT_EVENT_ONLY,  timeout);
+    }
+
+    /**
+     * Waits until {@code MockIme} does not send {@code "onInputViewLayoutChanged"} event
+     * for a certain period of time ({@code stableThresholdTime} msec).
+     *
+     * <p>When this returns non-null {@link ImeLayoutInfo}, the stream position will be set to
+     * the next event of the returned layout event.  Otherwise this method does not change stream
+     * position.</p>
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param stableThresholdTime threshold time to consider that {@link MockIme}'s layout is
+     *                            stable, in millisecond
+     * @return last {@link ImeLayoutInfo} if {@link MockIme} sent one or more
+     *         {@code "onInputViewLayoutChanged"} event.  Otherwise {@code null}
+     */
+    public static ImeLayoutInfo waitForInputViewLayoutStable(@NonNull ImeEventStream stream,
+            long stableThresholdTime) {
+        ImeLayoutInfo lastLayout = null;
+        final Predicate<ImeEvent> layoutFilter = event ->
+                !event.isEnterEvent() && event.getEventName().equals("onInputViewLayoutChanged");
+        try {
+            long deadline = SystemClock.elapsedRealtime() + stableThresholdTime;
+            while (true) {
+                if (deadline < SystemClock.elapsedRealtime()) {
+                    return lastLayout;
+                }
+                final Optional<ImeEvent> event = stream.seekToFirst(layoutFilter);
+                if (event.isPresent()) {
+                    // Remember the last event and extend the deadline again.
+                    lastLayout = ImeLayoutInfo.readFromBundle(event.get().getArguments());
+                    deadline = SystemClock.elapsedRealtime() + stableThresholdTime;
+                    stream.skip(1);
+                }
+                Thread.sleep(TIME_SLICE);
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException("notExpectEvent failed: " + stream.dump(), e);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/ImeLayoutInfo.java b/tests/autofillservice/src/com/android/cts/mockime/ImeLayoutInfo.java
new file mode 100644
index 0000000..77718ea
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/ImeLayoutInfo.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mockime;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowInsets;
+
+/**
+ * A collection of layout-related information when
+ * {@link View.OnLayoutChangeListener#onLayoutChange(View, int, int, int, int, int, int, int, int)}
+ * is called back for the input view (the view returned from {@link MockIme#onCreateInputView()}).
+ */
+public final class ImeLayoutInfo {
+
+    private static final String NEW_LAYOUT_KEY = "newLayout";
+    private static final String OLD_LAYOUT_KEY = "oldLayout";
+    private static final String VIEW_ORIGIN_ON_SCREEN_KEY = "viewOriginOnScreen";
+    private static final String DISPLAY_SIZE_KEY = "displaySize";
+    private static final String SYSTEM_WINDOW_INSET_KEY = "systemWindowInset";
+    private static final String STABLE_INSET_KEY = "stableInset";
+
+    @NonNull
+    private final Rect mNewLayout;
+    @NonNull
+    private final Rect mOldLayout;
+    @Nullable
+    private Point mViewOriginOnScreen;
+    @Nullable
+    private Point mDisplaySize;
+    @Nullable
+    private Rect mSystemWindowInset;
+    @Nullable
+    private Rect mStableInset;
+
+    /**
+     * Returns the bounding box of the {@link View} passed to
+     * {@link android.inputmethodservice.InputMethodService#onCreateInputView()} in screen
+     * coordinates.
+     *
+     * <p>Currently this method assumes that no {@link View} in the hierarchy uses
+     * transformations such as {@link View#setRotation(float)}.</p>
+     *
+     * @return Region in screen coordinates.
+     */
+    @Nullable
+    public Rect getInputViewBoundsInScreen() {
+        return new Rect(
+                mViewOriginOnScreen.x, mViewOriginOnScreen.y,
+                mViewOriginOnScreen.x + mNewLayout.width(),
+                mViewOriginOnScreen.y + mNewLayout.height());
+    }
+
+    /**
+     * Returns the screen area in screen coordinates that does not overlap with the system
+     * window inset, which represents the area of a full-screen window that is partially or
+     * fully obscured by the status bar, navigation bar, IME or other system windows.
+     *
+     * <p>May return {@code null} when this information is not yet ready.</p>
+     *
+     * @return Region in screen coordinates. {@code null} when it is not available
+     *
+     * @see WindowInsets#hasSystemWindowInsets()
+     * @see WindowInsets#getSystemWindowInsetBottom()
+     * @see WindowInsets#getSystemWindowInsetLeft()
+     * @see WindowInsets#getSystemWindowInsetRight()
+     * @see WindowInsets#getSystemWindowInsetTop()
+     */
+    @Nullable
+    public Rect getScreenRectWithoutSystemWindowInset() {
+        if (mDisplaySize == null) {
+            return null;
+        }
+        if (mSystemWindowInset == null) {
+            return new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
+        }
+        return new Rect(mSystemWindowInset.left, mSystemWindowInset.top,
+                mDisplaySize.x - mSystemWindowInset.right,
+                mDisplaySize.y - mSystemWindowInset.bottom);
+    }
+
+    /**
+     * Returns the screen area in screen coordinates that does not overlap with the stable
+     * inset, which represents the area of a full-screen window that <b>may</b> be partially or
+     * fully obscured by the system UI elements.
+     *
+     * <p>May return {@code null} when this information is not yet ready.</p>
+     *
+     * @return Region in screen coordinates. {@code null} when it is not available
+     *
+     * @see WindowInsets#hasStableInsets()
+     * @see WindowInsets#getStableInsetBottom()
+     * @see WindowInsets#getStableInsetLeft()
+     * @see WindowInsets#getStableInsetRight()
+     * @see WindowInsets#getStableInsetTop()
+     */
+    @Nullable
+    public Rect getScreenRectWithoutStableInset() {
+        if (mDisplaySize == null) {
+            return null;
+        }
+        if (mStableInset == null) {
+            return new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
+        }
+        return new Rect(mStableInset.left, mStableInset.top,
+                mDisplaySize.x - mStableInset.right,
+                mDisplaySize.y - mStableInset.bottom);
+    }
+
+    ImeLayoutInfo(@NonNull Rect newLayout, @NonNull Rect oldLayout,
+            @NonNull Point viewOriginOnScreen, @Nullable Point displaySize,
+            @Nullable Rect systemWindowInset, @Nullable Rect stableInset) {
+        mNewLayout = new Rect(newLayout);
+        mOldLayout = new Rect(oldLayout);
+        mViewOriginOnScreen = new Point(viewOriginOnScreen);
+        mDisplaySize = new Point(displaySize);
+        mSystemWindowInset = systemWindowInset;
+        mStableInset = stableInset;
+    }
+
+    void writeToBundle(@NonNull Bundle bundle) {
+        bundle.putParcelable(NEW_LAYOUT_KEY, mNewLayout);
+        bundle.putParcelable(OLD_LAYOUT_KEY, mOldLayout);
+        bundle.putParcelable(VIEW_ORIGIN_ON_SCREEN_KEY, mViewOriginOnScreen);
+        bundle.putParcelable(DISPLAY_SIZE_KEY, mDisplaySize);
+        bundle.putParcelable(SYSTEM_WINDOW_INSET_KEY, mSystemWindowInset);
+        bundle.putParcelable(STABLE_INSET_KEY, mStableInset);
+    }
+
+    static ImeLayoutInfo readFromBundle(@NonNull Bundle bundle) {
+        final Rect newLayout = bundle.getParcelable(NEW_LAYOUT_KEY);
+        final Rect oldLayout = bundle.getParcelable(OLD_LAYOUT_KEY);
+        final Point viewOrigin = bundle.getParcelable(VIEW_ORIGIN_ON_SCREEN_KEY);
+        final Point displaySize = bundle.getParcelable(DISPLAY_SIZE_KEY);
+        final Rect systemWindowInset = bundle.getParcelable(SYSTEM_WINDOW_INSET_KEY);
+        final Rect stableInset = bundle.getParcelable(STABLE_INSET_KEY);
+
+        return new ImeLayoutInfo(newLayout, oldLayout, viewOrigin, displaySize, systemWindowInset,
+                stableInset);
+    }
+
+    static ImeLayoutInfo fromLayoutListenerCallback(View v, int left, int top, int right,
+            int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+        final Rect newLayout = new Rect(left, top, right, bottom);
+        final Rect oldLayout = new Rect(oldLeft, oldTop, oldRight, oldBottom);
+        final int[] viewOriginArray = new int[2];
+        v.getLocationOnScreen(viewOriginArray);
+        final Point viewOrigin = new Point(viewOriginArray[0], viewOriginArray[1]);
+        final Display display = v.getDisplay();
+        final Point displaySize;
+        if (display != null) {
+            displaySize = new Point();
+            display.getRealSize(displaySize);
+        } else {
+            displaySize = null;
+        }
+        final WindowInsets windowInsets = v.getRootWindowInsets();
+        final Rect systemWindowInset;
+        if (windowInsets != null && windowInsets.hasSystemWindowInsets()) {
+            systemWindowInset = new Rect(
+                    windowInsets.getSystemWindowInsetLeft(), windowInsets.getSystemWindowInsetTop(),
+                    windowInsets.getSystemWindowInsetRight(),
+                    windowInsets.getSystemWindowInsetBottom());
+        } else {
+            systemWindowInset = null;
+        }
+        final Rect stableInset;
+        if (windowInsets != null && windowInsets.hasStableInsets()) {
+            stableInset = new Rect(
+                    windowInsets.getStableInsetLeft(), windowInsets.getStableInsetTop(),
+                    windowInsets.getStableInsetRight(), windowInsets.getStableInsetBottom());
+        } else {
+            stableInset = null;
+        }
+        return new ImeLayoutInfo(newLayout, oldLayout, viewOrigin, displaySize, systemWindowInset,
+                stableInset);
+    }
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/ImeSettings.java b/tests/autofillservice/src/com/android/cts/mockime/ImeSettings.java
new file mode 100644
index 0000000..21c25be
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/ImeSettings.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mockime;
+
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * An immutable data store to control the behavior of {@link MockIme}.
+ */
+public class ImeSettings {
+
+    @NonNull
+    private final String mClientPackageName;
+
+    @NonNull
+    private final String mEventCallbackActionName;
+
+    private static final String EVENT_CALLBACK_INTENT_ACTION_KEY = "eventCallbackActionName";
+    private static final String DATA_KEY = "data";
+
+    private static final String BACKGROUND_COLOR_KEY = "BackgroundColor";
+    private static final String NAVIGATION_BAR_COLOR_KEY = "NavigationBarColor";
+    private static final String INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET =
+            "InputViewHeightWithoutSystemWindowInset";
+    private static final String WINDOW_FLAGS = "WindowFlags";
+    private static final String WINDOW_FLAGS_MASK = "WindowFlagsMask";
+    private static final String FULLSCREEN_MODE_ALLOWED = "FullscreenModeAllowed";
+    private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility";
+    private static final String HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED =
+            "HardKeyboardConfigurationBehaviorAllowed";
+
+    @NonNull
+    private final PersistableBundle mBundle;
+
+    ImeSettings(@NonNull String clientPackageName, @NonNull Bundle bundle) {
+        mClientPackageName = clientPackageName;
+        mEventCallbackActionName = bundle.getString(EVENT_CALLBACK_INTENT_ACTION_KEY);
+        mBundle = bundle.getParcelable(DATA_KEY);
+    }
+
+    @Nullable
+    String getEventCallbackActionName() {
+        return mEventCallbackActionName;
+    }
+
+    @NonNull
+    String getClientPackageName() {
+        return mClientPackageName;
+    }
+
+    public boolean fullscreenModeAllowed(boolean defaultValue) {
+        return mBundle.getBoolean(FULLSCREEN_MODE_ALLOWED, defaultValue);
+    }
+
+    @ColorInt
+    public int getBackgroundColor(@ColorInt int defaultColor) {
+        return mBundle.getInt(BACKGROUND_COLOR_KEY, defaultColor);
+    }
+
+    public boolean hasNavigationBarColor() {
+        return mBundle.keySet().contains(NAVIGATION_BAR_COLOR_KEY);
+    }
+
+    @ColorInt
+    public int getNavigationBarColor() {
+        return mBundle.getInt(NAVIGATION_BAR_COLOR_KEY);
+    }
+
+    public int getInputViewHeightWithoutSystemWindowInset(int defaultHeight) {
+        return mBundle.getInt(INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET, defaultHeight);
+    }
+
+    public int getWindowFlags(int defaultFlags) {
+        return mBundle.getInt(WINDOW_FLAGS, defaultFlags);
+    }
+
+    public int getWindowFlagsMask(int defaultFlags) {
+        return mBundle.getInt(WINDOW_FLAGS_MASK, defaultFlags);
+    }
+
+    public int getInputViewSystemUiVisibility(int defaultFlags) {
+        return mBundle.getInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, defaultFlags);
+    }
+
+    public boolean getHardKeyboardConfigurationBehaviorAllowed(boolean defaultValue) {
+        return mBundle.getBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, defaultValue);
+    }
+
+    static Bundle serializeToBundle(@NonNull String eventCallbackActionName,
+            @Nullable Builder builder) {
+        final Bundle result = new Bundle();
+        result.putString(EVENT_CALLBACK_INTENT_ACTION_KEY, eventCallbackActionName);
+        result.putParcelable(DATA_KEY, builder != null ? builder.mBundle : PersistableBundle.EMPTY);
+        return result;
+    }
+
+    /**
+     * The builder class for {@link ImeSettings}.
+     */
+    public static final class Builder {
+        private final PersistableBundle mBundle = new PersistableBundle();
+
+        /**
+         * Controls whether fullscreen mode is allowed or not.
+         *
+         * <p>By default, fullscreen mode is not allowed in {@link MockIme}.</p>
+         *
+         * @param allowed {@code true} if fullscreen mode is allowed
+         * @see MockIme#onEvaluateFullscreenMode()
+         */
+        public Builder setFullscreenModeAllowed(boolean allowed) {
+            mBundle.putBoolean(FULLSCREEN_MODE_ALLOWED, allowed);
+            return this;
+        }
+
+        /**
+         * Sets the background color of the {@link MockIme}.
+         * @param color background color to be used
+         */
+        public Builder setBackgroundColor(@ColorInt int color) {
+            mBundle.putInt(BACKGROUND_COLOR_KEY, color);
+            return this;
+        }
+
+        /**
+         * Sets the color to be passed to {@link android.view.Window#setNavigationBarColor(int)}.
+         *
+         * @param color color to be passed to {@link android.view.Window#setNavigationBarColor(int)}
+         * @see android.view.View
+         */
+        public Builder setNavigationBarColor(@ColorInt int color) {
+            mBundle.putInt(NAVIGATION_BAR_COLOR_KEY, color);
+            return this;
+        }
+
+        /**
+         * Sets the input view height measured from the bottom system window inset.
+         * @param height height of the soft input view. This does not include the system window
+         *               inset such as navigation bar
+         */
+        public Builder setInputViewHeightWithoutSystemWindowInset(int height) {
+            mBundle.putInt(INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET, height);
+            return this;
+        }
+
+        /**
+         * Sets window flags to be specified to {@link android.view.Window#setFlags(int, int)} of
+         * the main {@link MockIme} window.
+         *
+         * <p>When {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} is set,
+         * {@link MockIme} tries to render the navigation bar by itself.</p>
+         *
+         * @param flags flags to be specified
+         * @param flagsMask mask bits that specify what bits need to be cleared before setting
+         *                  {@code flags}
+         * @see android.view.WindowManager
+         */
+        public Builder setWindowFlags(int flags, int flagsMask) {
+            mBundle.putInt(WINDOW_FLAGS, flags);
+            mBundle.putInt(WINDOW_FLAGS_MASK, flagsMask);
+            return this;
+        }
+
+        /**
+         * Sets flags to be specified to {@link android.view.View#setSystemUiVisibility(int)} of
+         * the main soft input view (the returned view from {@link MockIme#onCreateInputView()}).
+         *
+         * @param visibilityFlags flags to be specified
+         * @see android.view.View
+         */
+        public Builder setInputViewSystemUiVisibility(int visibilityFlags) {
+            mBundle.putInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, visibilityFlags);
+            return this;
+        }
+
+        /**
+         * Controls whether {@link MockIme} is allowed to change the behavior based on
+         * {@link android.content.res.Configuration#keyboard} and
+         * {@link android.content.res.Configuration#hardKeyboardHidden}.
+         *
+         * <p>Methods in {@link android.inputmethodservice.InputMethodService} such as
+         * {@link android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()} and
+         * {@link android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)}
+         * change their behaviors when a hardware keyboard is attached.  This is confusing when
+         * writing tests so by default {@link MockIme} tries to cancel those behaviors.  This
+         * settings re-enables such a behavior.</p>
+         *
+         * @param allowed {@code true} when {@link MockIme} is allowed to change the behavior when
+         *                a hardware keyboard is attached
+         *
+         * @see android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()
+         * @see android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)
+         */
+        public Builder setHardKeyboardConfigurationBehaviorAllowed(boolean allowed) {
+            mBundle.putBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, allowed);
+            return this;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/ImeState.java b/tests/autofillservice/src/com/android/cts/mockime/ImeState.java
new file mode 100644
index 0000000..0135b30
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/ImeState.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.mockime;
+
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * An immutable object that stores several runtime state of {@link MockIme}.
+ */
+public final class ImeState {
+    private final boolean mHasInputBinding;
+    private final boolean mHasDummyInputConnection;
+
+    /**
+     * @return {@code true} if {@link MockIme#getCurrentInputBinding()} returned non-null
+     *         {@link android.view.inputmethod.InputBinding} when this snapshot was taken.
+     */
+    public boolean hasInputBinding() {
+        return mHasInputBinding;
+    }
+
+    /**
+     * @return {@code true} if {@link MockIme#getCurrentInputConnection()} returned non-dummy
+     *         {@link android.view.inputmethod.InputConnection} when this snapshot was taken.
+     */
+    public boolean hasDummyInputConnection() {
+        return mHasDummyInputConnection;
+    }
+
+    ImeState(boolean hasInputBinding, boolean hasDummyInputConnection) {
+        mHasInputBinding = hasInputBinding;
+        mHasDummyInputConnection = hasDummyInputConnection;
+    }
+
+    @NonNull
+    Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("mHasInputBinding", mHasInputBinding);
+        bundle.putBoolean("mHasDummyInputConnection", mHasDummyInputConnection);
+        return bundle;
+    }
+
+    @Nullable
+    static ImeState fromBundle(@Nullable Bundle bundle) {
+        if (bundle == null) {
+            return null;
+        }
+        final boolean hasInputBinding = bundle.getBoolean("mHasInputBinding");
+        final boolean hasDummyInputConnection = bundle.getBoolean("mHasDummyInputConnection");
+        return new ImeState(hasInputBinding, hasDummyInputConnection);
+    }
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/MockIme.java b/tests/autofillservice/src/com/android/cts/mockime/MockIme.java
new file mode 100644
index 0000000..52a1182
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/MockIme.java
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mockime;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
+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.inputmethodservice.InputMethodService;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import androidx.annotation.AnyThread;
+import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethod;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Mock IME for end-to-end tests.
+ */
+public final class MockIme extends InputMethodService {
+
+    private static final String TAG = "MockIme";
+
+    private static final String PACKAGE_NAME = "android.autofillservice.cts";
+
+    static ComponentName getComponentName() {
+        return new ComponentName(PACKAGE_NAME, MockIme.class.getName());
+    }
+
+    static String getImeId() {
+        return getComponentName().flattenToShortString();
+    }
+
+    static String getCommandActionName(@NonNull String eventActionName) {
+        return eventActionName + ".command";
+    }
+
+    private final HandlerThread mHandlerThread = new HandlerThread("CommandReceiver");
+
+    private final Handler mMainHandler = new Handler();
+
+    private static final class CommandReceiver extends BroadcastReceiver {
+        @NonNull
+        private final String mActionName;
+        @NonNull
+        private final Consumer<ImeCommand> mOnReceiveCommand;
+
+        CommandReceiver(@NonNull String actionName,
+                @NonNull Consumer<ImeCommand> onReceiveCommand) {
+            mActionName = actionName;
+            mOnReceiveCommand = onReceiveCommand;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (TextUtils.equals(mActionName, intent.getAction())) {
+                mOnReceiveCommand.accept(ImeCommand.fromBundle(intent.getExtras()));
+            }
+        }
+    }
+
+    @WorkerThread
+    private void onReceiveCommand(@NonNull ImeCommand command) {
+        getTracer().onReceiveCommand(command, () -> {
+            if (command.shouldDispatchToMainThread()) {
+                mMainHandler.post(() -> onHandleCommand(command));
+            } else {
+                onHandleCommand(command);
+            }
+        });
+    }
+
+    @AnyThread
+    private void onHandleCommand(@NonNull ImeCommand command) {
+        getTracer().onHandleCommand(command, () -> {
+            if (command.shouldDispatchToMainThread()) {
+                if (Looper.myLooper() != Looper.getMainLooper()) {
+                    throw new IllegalStateException("command " + command
+                            + " should be handled on the main thread");
+                }
+                switch (command.getName()) {
+                    case "commitText": {
+                        final CharSequence text = command.getExtras().getString("text");
+                        final int newCursorPosition =
+                                command.getExtras().getInt("newCursorPosition");
+                        getCurrentInputConnection().commitText(text, newCursorPosition);
+                        break;
+                    }
+                    case "setBackDisposition": {
+                        final int backDisposition =
+                                command.getExtras().getInt("backDisposition");
+                        setBackDisposition(backDisposition);
+                        break;
+                    }
+                    case "requestHideSelf": {
+                        final int flags = command.getExtras().getInt("flags");
+                        requestHideSelf(flags);
+                        break;
+                    }
+                    case "requestShowSelf": {
+                        final int flags = command.getExtras().getInt("flags");
+                        requestShowSelf(flags);
+                        break;
+                    }
+                }
+            }
+        });
+    }
+
+    @Nullable
+    private CommandReceiver mCommandReceiver;
+
+    @Nullable
+    private ImeSettings mSettings;
+
+    private final AtomicReference<String> mImeEventActionName = new AtomicReference<>();
+
+    @Nullable
+    String getImeEventActionName() {
+        return mImeEventActionName.get();
+    }
+
+    private final AtomicReference<String> mClientPackageName = new AtomicReference<>();
+
+    @Nullable
+    String getClientPackageName() {
+        return mClientPackageName.get();
+    }
+
+    private class MockInputMethodImpl extends InputMethodImpl {
+        @Override
+        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+            getTracer().showSoftInput(flags, resultReceiver,
+                    () -> super.showSoftInput(flags, resultReceiver));
+        }
+
+        @Override
+        public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
+            getTracer().hideSoftInput(flags, resultReceiver,
+                    () -> super.hideSoftInput(flags, resultReceiver));
+        }
+
+        @Override
+        public void attachToken(IBinder token) {
+            getTracer().attachToken(token, () -> super.attachToken(token));
+        }
+
+        @Override
+        public void bindInput(InputBinding binding) {
+            getTracer().bindInput(binding, () -> super.bindInput(binding));
+        }
+
+        @Override
+        public void unbindInput() {
+            getTracer().unbindInput(() -> super.unbindInput());
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        // Initialize minimum settings to send events in Tracer#onCreate().
+        mSettings = SettingsProvider.getSettings();
+        if (mSettings == null) {
+            throw new IllegalStateException("Settings file is not found. "
+                    + "Make sure MockImeSession.create() is used to launch Mock IME.");
+        }
+        mClientPackageName.set(mSettings.getClientPackageName());
+        mImeEventActionName.set(mSettings.getEventCallbackActionName());
+
+        getTracer().onCreate(() -> {
+            super.onCreate();
+            mHandlerThread.start();
+            final String actionName = getCommandActionName(mSettings.getEventCallbackActionName());
+            mCommandReceiver = new CommandReceiver(actionName, this::onReceiveCommand);
+            final IntentFilter filter = new IntentFilter(actionName);
+            final Handler handler = new Handler(mHandlerThread.getLooper());
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler,
+                        Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
+            } else {
+                registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler);
+            }
+
+            final int windowFlags = mSettings.getWindowFlags(0);
+            final int windowFlagsMask = mSettings.getWindowFlagsMask(0);
+            if (windowFlags != 0 || windowFlagsMask != 0) {
+                final int prevFlags = getWindow().getWindow().getAttributes().flags;
+                getWindow().getWindow().setFlags(windowFlags, windowFlagsMask);
+                // For some reasons, seems that we need to post another requestLayout() when
+                // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS bit is changed.
+                // TODO: Investigate the reason.
+                if ((windowFlagsMask & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+                    final boolean hadFlag = (prevFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+                    final boolean hasFlag = (windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+                    if (hadFlag != hasFlag) {
+                        final View decorView = getWindow().getWindow().getDecorView();
+                        decorView.post(() -> decorView.requestLayout());
+                    }
+                }
+            }
+
+            if (mSettings.hasNavigationBarColor()) {
+                getWindow().getWindow().setNavigationBarColor(mSettings.getNavigationBarColor());
+            }
+        });
+    }
+
+    @Override
+    public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) {
+        getTracer().onConfigureWindow(win, isFullscreen, isCandidatesOnly,
+                () -> super.onConfigureWindow(win, isFullscreen, isCandidatesOnly));
+    }
+
+    @Override
+    public boolean onEvaluateFullscreenMode() {
+        return getTracer().onEvaluateFullscreenMode(() ->
+                mSettings.fullscreenModeAllowed(false) && super.onEvaluateFullscreenMode());
+    }
+
+    private static final class KeyboardLayoutView extends LinearLayout {
+        @NonNull
+        private final ImeSettings mSettings;
+        @NonNull
+        private final View.OnLayoutChangeListener mLayoutListener;
+
+        KeyboardLayoutView(Context context, @NonNull ImeSettings imeSettings,
+                @Nullable Consumer<ImeLayoutInfo> onInputViewLayoutChangedCallback) {
+            super(context);
+
+            mSettings = imeSettings;
+
+            setOrientation(VERTICAL);
+
+            final int defaultBackgroundColor =
+                    getResources().getColor(android.R.color.holo_orange_dark, null);
+            setBackgroundColor(mSettings.getBackgroundColor(defaultBackgroundColor));
+
+            final int mainSpacerHeight = mSettings.getInputViewHeightWithoutSystemWindowInset(
+                    LayoutParams.WRAP_CONTENT);
+            {
+                final RelativeLayout layout = new RelativeLayout(getContext());
+                final TextView textView = new TextView(getContext());
+                final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
+                        RelativeLayout.LayoutParams.MATCH_PARENT,
+                        RelativeLayout.LayoutParams.WRAP_CONTENT);
+                params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
+                textView.setLayoutParams(params);
+                textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
+                textView.setGravity(Gravity.CENTER);
+                textView.setText(getImeId());
+                layout.addView(textView);
+                addView(layout, LayoutParams.MATCH_PARENT, mainSpacerHeight);
+            }
+
+            final int systemUiVisibility = mSettings.getInputViewSystemUiVisibility(0);
+            if (systemUiVisibility != 0) {
+                setSystemUiVisibility(systemUiVisibility);
+            }
+
+            mLayoutListener = (View v, int left, int top, int right, int bottom, int oldLeft,
+                    int oldTop, int oldRight, int oldBottom) ->
+                    onInputViewLayoutChangedCallback.accept(
+                            ImeLayoutInfo.fromLayoutListenerCallback(
+                                    v, left, top, right, bottom, oldLeft, oldTop, oldRight,
+                                    oldBottom));
+            this.addOnLayoutChangeListener(mLayoutListener);
+        }
+
+        private void updateBottomPaddingIfNecessary(int newPaddingBottom) {
+            if (getPaddingBottom() != newPaddingBottom) {
+                setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), newPaddingBottom);
+            }
+        }
+
+        @Override
+        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+            if (insets.isConsumed()
+                    || (getSystemUiVisibility() & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0) {
+                // In this case we are not interested in consuming NavBar region.
+                // Make sure that the bottom padding is empty.
+                updateBottomPaddingIfNecessary(0);
+                return insets;
+            }
+
+            // In some cases the bottom system window inset is not a navigation bar. Wear devices
+            // that have bottom chin are examples.  For now, assume that it's a navigation bar if it
+            // has the same height as the root window's stable bottom inset.
+            final WindowInsets rootWindowInsets = getRootWindowInsets();
+            if (rootWindowInsets != null && (rootWindowInsets.getStableInsetBottom()
+                    != insets.getSystemWindowInsetBottom())) {
+                // This is probably not a NavBar.
+                updateBottomPaddingIfNecessary(0);
+                return insets;
+            }
+
+            final int possibleNavBarHeight = insets.getSystemWindowInsetBottom();
+            updateBottomPaddingIfNecessary(possibleNavBarHeight);
+            return possibleNavBarHeight <= 0
+                    ? insets
+                    : insets.replaceSystemWindowInsets(
+                            insets.getSystemWindowInsetLeft(),
+                            insets.getSystemWindowInsetTop(),
+                            insets.getSystemWindowInsetRight(),
+                            0 /* bottom */);
+        }
+
+        @Override
+        protected void onDetachedFromWindow() {
+            super.onDetachedFromWindow();
+            removeOnLayoutChangeListener(mLayoutListener);
+        }
+    }
+
+    private void onInputViewLayoutChanged(@NonNull ImeLayoutInfo layoutInfo) {
+        getTracer().onInputViewLayoutChanged(layoutInfo, () -> { });
+    }
+
+    @Override
+    public View onCreateInputView() {
+        return getTracer().onCreateInputView(() ->
+                new KeyboardLayoutView(this, mSettings, this::onInputViewLayoutChanged));
+    }
+
+    @Override
+    public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+        getTracer().onStartInput(editorInfo, restarting,
+                () -> super.onStartInput(editorInfo, restarting));
+    }
+
+    @Override
+    public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+        getTracer().onStartInputView(editorInfo, restarting,
+                () -> super.onStartInputView(editorInfo, restarting));
+    }
+
+    @Override
+    public void onFinishInputView(boolean finishingInput) {
+        getTracer().onFinishInputView(finishingInput,
+                () -> super.onFinishInputView(finishingInput));
+    }
+
+    @Override
+    public void onFinishInput() {
+        getTracer().onFinishInput(() -> super.onFinishInput());
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return getTracer().onKeyDown(keyCode, event, () -> super.onKeyDown(keyCode, event));
+    }
+
+    @CallSuper
+    public boolean onEvaluateInputViewShown() {
+        return getTracer().onEvaluateInputViewShown(() -> {
+            // onShowInputRequested() is indeed @CallSuper so we always call this, even when the
+            // result is ignored.
+            final boolean originalResult = super.onEvaluateInputViewShown();
+            if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
+                final Configuration config = getResources().getConfiguration();
+                if (config.keyboard != Configuration.KEYBOARD_NOKEYS
+                        && config.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES) {
+                    // Override the behavior of InputMethodService#onEvaluateInputViewShown()
+                    return true;
+                }
+            }
+            return originalResult;
+        });
+    }
+
+    @Override
+    public boolean onShowInputRequested(int flags, boolean configChange) {
+        return getTracer().onShowInputRequested(flags, configChange, () -> {
+            // onShowInputRequested() is not marked with @CallSuper, but just in case.
+            final boolean originalResult = super.onShowInputRequested(flags, configChange);
+            if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
+                if ((flags & InputMethod.SHOW_EXPLICIT) == 0
+                        && getResources().getConfiguration().keyboard
+                        != Configuration.KEYBOARD_NOKEYS) {
+                    // Override the behavior of InputMethodService#onShowInputRequested()
+                    return true;
+                }
+            }
+            return originalResult;
+        });
+    }
+
+    @Override
+    public void onDestroy() {
+        getTracer().onDestroy(() -> {
+            super.onDestroy();
+            unregisterReceiver(mCommandReceiver);
+            mHandlerThread.quitSafely();
+        });
+    }
+
+    @Override
+    public AbstractInputMethodImpl onCreateInputMethodInterface() {
+        return getTracer().onCreateInputMethodInterface(() -> new MockInputMethodImpl());
+    }
+
+    private final ThreadLocal<Tracer> mThreadLocalTracer = new ThreadLocal<>();
+
+    private Tracer getTracer() {
+        Tracer tracer = mThreadLocalTracer.get();
+        if (tracer == null) {
+            tracer = new Tracer(this);
+            mThreadLocalTracer.set(tracer);
+        }
+        return tracer;
+    }
+
+    @NonNull
+    private ImeState getState() {
+        final boolean hasInputBinding = getCurrentInputBinding() != null;
+        final boolean hasDummyInputConnectionConnection =
+                !hasInputBinding
+                        || getCurrentInputConnection() == getCurrentInputBinding().getConnection();
+        return new ImeState(hasInputBinding, hasDummyInputConnectionConnection);
+    }
+
+    /**
+     * Event tracing helper class for {@link MockIme}.
+     */
+    private static final class Tracer {
+
+        @NonNull
+        private final MockIme mIme;
+
+        private final int mThreadId = Process.myTid();
+
+        @NonNull
+        private final String mThreadName =
+                Thread.currentThread().getName() != null ? Thread.currentThread().getName() : "";
+
+        private final boolean mIsMainThread =
+                Looper.getMainLooper().getThread() == Thread.currentThread();
+
+        private int mNestLevel = 0;
+
+        private String mImeEventActionName;
+
+        private String mClientPackageName;
+
+        Tracer(@NonNull MockIme mockIme) {
+            mIme = mockIme;
+        }
+
+        private void sendEventInternal(@NonNull ImeEvent event) {
+            if (mImeEventActionName == null) {
+                mImeEventActionName = mIme.getImeEventActionName();
+            }
+            if (mClientPackageName == null) {
+                mClientPackageName = mIme.getClientPackageName();
+            }
+            if (mImeEventActionName == null || mClientPackageName == null) {
+                Log.e(TAG, "Tracer cannot be used before onCreate()");
+                return;
+            }
+            final Intent intent = new Intent()
+                    .setAction(mImeEventActionName)
+                    .setPackage(mClientPackageName)
+                    .putExtras(event.toBundle())
+                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+            mIme.sendBroadcast(intent);
+        }
+
+        private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable) {
+            recordEventInternal(eventName, runnable, new Bundle());
+        }
+
+        private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable,
+                @NonNull Bundle arguments) {
+            recordEventInternal(eventName, () -> {
+                runnable.run(); return null;
+            }, arguments);
+        }
+
+        private <T> T recordEventInternal(@NonNull String eventName,
+                @NonNull Supplier<T> supplier) {
+            return recordEventInternal(eventName, supplier, new Bundle());
+        }
+
+        private <T> T recordEventInternal(@NonNull String eventName,
+                @NonNull Supplier<T> supplier, @NonNull Bundle arguments) {
+            final ImeState enterState = mIme.getState();
+            final long enterTimestamp = SystemClock.elapsedRealtimeNanos();
+            final long enterWallTime = System.currentTimeMillis();
+            final int nestLevel = mNestLevel;
+            // Send enter event
+            sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName,
+                    mThreadId, mIsMainThread, enterTimestamp, 0, enterWallTime,
+                    0, enterState, null, arguments, null));
+            ++mNestLevel;
+            T result;
+            try {
+                result = supplier.get();
+            } finally {
+                --mNestLevel;
+            }
+            final long exitTimestamp = SystemClock.elapsedRealtimeNanos();
+            final long exitWallTime = System.currentTimeMillis();
+            final ImeState exitState = mIme.getState();
+            // Send exit event
+            sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName,
+                    mThreadId, mIsMainThread, enterTimestamp, exitTimestamp, enterWallTime,
+                    exitWallTime, enterState, exitState, arguments, result));
+            return result;
+        }
+
+        public void onCreate(@NonNull Runnable runnable) {
+            recordEventInternal("onCreate", runnable);
+        }
+
+        public void onConfigureWindow(Window win, boolean isFullscreen,
+                boolean isCandidatesOnly, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBoolean("isFullscreen", isFullscreen);
+            arguments.putBoolean("isCandidatesOnly", isCandidatesOnly);
+            recordEventInternal("onConfigureWindow", runnable, arguments);
+        }
+
+        public boolean onEvaluateFullscreenMode(@NonNull BooleanSupplier supplier) {
+            return recordEventInternal("onEvaluateFullscreenMode", supplier::getAsBoolean);
+        }
+
+        public boolean onEvaluateInputViewShown(@NonNull BooleanSupplier supplier) {
+            return recordEventInternal("onEvaluateInputViewShown", supplier::getAsBoolean);
+        }
+
+        public View onCreateInputView(@NonNull Supplier<View> supplier) {
+            return recordEventInternal("onCreateInputView", supplier);
+        }
+
+        public void onStartInput(EditorInfo editorInfo, boolean restarting,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putParcelable("editorInfo", editorInfo);
+            arguments.putBoolean("restarting", restarting);
+            recordEventInternal("onStartInput", runnable, arguments);
+        }
+
+        public void onStartInputView(EditorInfo editorInfo, boolean restarting,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putParcelable("editorInfo", editorInfo);
+            arguments.putBoolean("restarting", restarting);
+            recordEventInternal("onStartInputView", runnable, arguments);
+        }
+
+        public void onFinishInputView(boolean finishingInput, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBoolean("finishingInput", finishingInput);
+            recordEventInternal("onFinishInputView", runnable, arguments);
+        }
+
+        public void onFinishInput(@NonNull Runnable runnable) {
+            recordEventInternal("onFinishInput", runnable);
+        }
+
+        public boolean onKeyDown(int keyCode, KeyEvent event, @NonNull BooleanSupplier supplier) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("keyCode", keyCode);
+            arguments.putParcelable("event", event);
+            return recordEventInternal("onKeyDown", supplier::getAsBoolean, arguments);
+        }
+
+        public boolean onShowInputRequested(int flags, boolean configChange,
+                @NonNull BooleanSupplier supplier) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("flags", flags);
+            arguments.putBoolean("configChange", configChange);
+            return recordEventInternal("onShowInputRequested", supplier::getAsBoolean, arguments);
+        }
+
+        public void onDestroy(@NonNull Runnable runnable) {
+            recordEventInternal("onDestroy", runnable);
+        }
+
+        public void attachToken(IBinder token, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBinder("token", token);
+            recordEventInternal("attachToken", runnable, arguments);
+        }
+
+        public void bindInput(InputBinding binding, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putParcelable("binding", binding);
+            recordEventInternal("bindInput", runnable, arguments);
+        }
+
+        public void unbindInput(@NonNull Runnable runnable) {
+            recordEventInternal("unbindInput", runnable);
+        }
+
+        public void showSoftInput(int flags, ResultReceiver resultReceiver,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("flags", flags);
+            arguments.putParcelable("resultReceiver", resultReceiver);
+            recordEventInternal("showSoftInput", runnable, arguments);
+        }
+
+        public void hideSoftInput(int flags, ResultReceiver resultReceiver,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("flags", flags);
+            arguments.putParcelable("resultReceiver", resultReceiver);
+            recordEventInternal("hideSoftInput", runnable, arguments);
+        }
+
+        public AbstractInputMethodImpl onCreateInputMethodInterface(
+                @NonNull Supplier<AbstractInputMethodImpl> supplier) {
+            return recordEventInternal("onCreateInputMethodInterface", supplier);
+        }
+
+        public void onReceiveCommand(
+                @NonNull ImeCommand command, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBundle("command", command.toBundle());
+            recordEventInternal("onReceiveCommand", runnable, arguments);
+        }
+
+        public void onHandleCommand(
+                @NonNull ImeCommand command, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBundle("command", command.toBundle());
+            recordEventInternal("onHandleCommand", runnable, arguments);
+        }
+
+        public void onInputViewLayoutChanged(@NonNull ImeLayoutInfo imeLayoutInfo,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            imeLayoutInfo.writeToBundle(arguments);
+            recordEventInternal("onInputViewLayoutChanged", runnable, arguments);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/MockImeSession.java b/tests/autofillservice/src/com/android/cts/mockime/MockImeSession.java
new file mode 100644
index 0000000..727da2cf
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/MockImeSession.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mockime;
+
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.Settings;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents an active Mock IME session, which provides basic primitives to write end-to-end tests
+ * for IME APIs.
+ *
+ * <p>To use {@link MockIme} via {@link MockImeSession}, you need to </p>
+ * <p>Public methods are not thread-safe.</p>
+ */
+public class MockImeSession implements AutoCloseable {
+    private final String mImeEventActionName =
+            "com.android.cts.mockime.action.IME_EVENT." + SystemClock.elapsedRealtimeNanos();
+
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10);
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final UiAutomation mUiAutomation;
+
+    private final HandlerThread mHandlerThread = new HandlerThread("EventReceiver");
+
+    private static final class EventStore {
+        private static final int INITIAL_ARRAY_SIZE = 32;
+
+        @NonNull
+        public final ImeEvent[] mArray;
+        public int mLength;
+
+        EventStore() {
+            mArray = new ImeEvent[INITIAL_ARRAY_SIZE];
+            mLength = 0;
+        }
+
+        EventStore(EventStore src, int newLength) {
+            mArray = new ImeEvent[newLength];
+            mLength = src.mLength;
+            System.arraycopy(src.mArray, 0, mArray, 0, src.mLength);
+        }
+
+        public EventStore add(ImeEvent event) {
+            if (mLength + 1 <= mArray.length) {
+                mArray[mLength] = event;
+                ++mLength;
+                return this;
+            } else {
+                return new EventStore(this, mLength * 2).add(event);
+            }
+        }
+
+        public ImeEventStream.ImeEventArray takeSnapshot() {
+            return new ImeEventStream.ImeEventArray(mArray, mLength);
+        }
+    }
+
+    private static final class MockImeEventReceiver extends BroadcastReceiver {
+        private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
+        @NonNull
+        private EventStore mCurrentEventStore = new EventStore();
+
+        @NonNull
+        private final String mActionName;
+
+        MockImeEventReceiver(@NonNull String actionName) {
+            mActionName = actionName;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (TextUtils.equals(mActionName, intent.getAction())) {
+                synchronized (mLock) {
+                    mCurrentEventStore =
+                            mCurrentEventStore.add(ImeEvent.fromBundle(intent.getExtras()));
+                }
+            }
+        }
+
+        public ImeEventStream.ImeEventArray takeEventSnapshot() {
+            synchronized (mLock) {
+                return mCurrentEventStore.takeSnapshot();
+            }
+        }
+    }
+    private final MockImeEventReceiver mEventReceiver =
+            new MockImeEventReceiver(mImeEventActionName);
+
+    private final ImeEventStream mEventStream =
+            new ImeEventStream(mEventReceiver::takeEventSnapshot);
+
+    private static String executeShellCommand(
+            @NonNull UiAutomation uiAutomation, @NonNull String command) throws IOException {
+        try (ParcelFileDescriptor.AutoCloseInputStream in =
+                     new ParcelFileDescriptor.AutoCloseInputStream(
+                             uiAutomation.executeShellCommand(command))) {
+            final StringBuilder sb = new StringBuilder();
+            final byte[] buffer = new byte[4096];
+            while (true) {
+                final int numRead = in.read(buffer);
+                if (numRead <= 0) {
+                    break;
+                }
+                sb.append(new String(buffer, 0, numRead));
+            }
+            return sb.toString();
+        }
+    }
+
+    @Nullable
+    private String getCurrentInputMethodId() {
+        // TODO: Replace this with IMM#getCurrentInputMethodIdForTesting()
+        return Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_INPUT_METHOD);
+    }
+
+    @Nullable
+    private static void writeMockImeSettings(@NonNull Context context,
+            @NonNull String imeEventActionName,
+            @Nullable ImeSettings.Builder imeSettings) throws Exception {
+        final Bundle bundle = ImeSettings.serializeToBundle(imeEventActionName, imeSettings);
+        context.getContentResolver().call(SettingsProvider.AUTHORITY, "write", null, bundle);
+    }
+
+    private ComponentName getMockImeComponentName() {
+        return MockIme.getComponentName();
+    }
+
+    private String getMockImeId() {
+        return MockIme.getImeId();
+    }
+
+    private MockImeSession(@NonNull Context context, @NonNull UiAutomation uiAutomation) {
+        mContext = context;
+        mUiAutomation = uiAutomation;
+    }
+
+    private void initialize(@Nullable ImeSettings.Builder imeSettings) throws Exception {
+        // Make sure that MockIME is not selected.
+        if (mContext.getSystemService(InputMethodManager.class)
+                .getInputMethodList()
+                .stream()
+                .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
+            executeShellCommand(mUiAutomation, "ime reset");
+        }
+        if (mContext.getSystemService(InputMethodManager.class)
+                .getEnabledInputMethodList()
+                .stream()
+                .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
+            throw new IllegalStateException();
+        }
+
+        writeMockImeSettings(mContext, mImeEventActionName, imeSettings);
+
+        mHandlerThread.start();
+        mContext.registerReceiver(mEventReceiver,
+                new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
+                new Handler(mHandlerThread.getLooper()));
+
+        executeShellCommand(mUiAutomation, "ime enable " + getMockImeId());
+        executeShellCommand(mUiAutomation, "ime set " + getMockImeId());
+
+        PollingCheck.check("Make sure that MockIME becomes available", TIMEOUT,
+                () -> getMockImeId().equals(getCurrentInputMethodId()));
+    }
+
+    /**
+     * Creates a new Mock IME session. During this session, you can receive various events from
+     * {@link MockIme}.
+     *
+     * @param context {@link Context} to be used to receive inter-process events from the
+     *                {@link MockIme} (e.g. via {@link BroadcastReceiver}
+     * @param uiAutomation {@link UiAutomation} object to change the device state that are typically
+     *                     guarded by permissions.
+     * @param imeSettings Key-value pairs to be passed to the {@link MockIme}.
+     * @return A session object, with which you can retrieve event logs from the {@link MockIme} and
+     *         can clean up the session.
+     */
+    @NonNull
+    public static MockImeSession create(
+            @NonNull Context context,
+            @NonNull UiAutomation uiAutomation,
+            @Nullable ImeSettings.Builder imeSettings) throws Exception {
+        final MockImeSession client = new MockImeSession(context, uiAutomation);
+        client.initialize(imeSettings);
+        return client;
+    }
+
+    /**
+     * @return {@link ImeEventStream} object that stores events sent from {@link MockIme} since the
+     *         session is created.
+     */
+    public ImeEventStream openEventStream() {
+        return mEventStream.copy();
+    }
+
+    /**
+     * Closes the active session and de-selects {@link MockIme}. Currently which IME will be
+     * selected next is up to the system.
+     */
+    public void close() throws Exception {
+        executeShellCommand(mUiAutomation, "ime reset");
+
+        PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
+                mContext.getSystemService(InputMethodManager.class)
+                        .getEnabledInputMethodList()
+                        .stream()
+                        .noneMatch(info -> getMockImeComponentName().equals(info.getComponent())));
+
+        mContext.unregisterReceiver(mEventReceiver);
+        mHandlerThread.quitSafely();
+        mContext.getContentResolver().call(SettingsProvider.AUTHORITY, "delete", null, null);
+    }
+
+    /**
+     * Lets {@link MockIme} to call
+     * {@link android.view.inputmethod.InputConnection#commitText(CharSequence, int)} with the given
+     * parameters.
+     *
+     * <p>This triggers {@code getCurrentInputConnection().commitText(text, newCursorPosition)}.</p>
+     *
+     * @param text to be passed as the {@code text} parameter
+     * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
+     * @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 callCommitText(@NonNull CharSequence text, int newCursorPosition) {
+        final Bundle params = new Bundle();
+        params.putCharSequence("text", text);
+        params.putInt("newCursorPosition", newCursorPosition);
+        final ImeCommand command = new ImeCommand(
+                "commitText", SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(MockIme.getComponentName().getPackageName());
+        intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+
+    @NonNull
+    public ImeCommand callSetBackDisposition(int backDisposition) {
+        final Bundle params = new Bundle();
+        params.putInt("backDisposition", backDisposition);
+        final ImeCommand command = new ImeCommand(
+                "setBackDisposition", SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(MockIme.getComponentName().getPackageName());
+        intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+
+    @NonNull
+    public ImeCommand callRequestHideSelf(int flags) {
+        final Bundle params = new Bundle();
+        params.putInt("flags", flags);
+        final ImeCommand command = new ImeCommand(
+                "requestHideSelf", SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(MockIme.getComponentName().getPackageName());
+        intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+
+    @NonNull
+    public ImeCommand callRequestShowSelf(int flags) {
+        final Bundle params = new Bundle();
+        params.putInt("flags", flags);
+        final ImeCommand command = new ImeCommand(
+                "requestShowSelf", SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(MockIme.getComponentName().getPackageName());
+        intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/MockImeSessionRule.java b/tests/autofillservice/src/com/android/cts/mockime/MockImeSessionRule.java
new file mode 100644
index 0000000..8632c3b
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/MockImeSessionRule.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.mockime;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.mockime.ImeSettings.Builder;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Custom JUnit4 rule to automatically open and close a {@link MockImeSession}.
+ */
+public final class MockImeSessionRule implements TestRule {
+
+    private static final String TAG = MockImeSessionRule.class.getSimpleName();
+    private final Context mContext;
+    private final Builder mImeSettings;
+    private final UiAutomation mUiAutomation;
+    private MockImeSession mMockImeSession;
+    private boolean mIgnoreInitException;
+    private Throwable mInitException;
+
+    public MockImeSessionRule() {
+        this(InstrumentationRegistry.getTargetContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder(), /* ignoreInitException= */ false);
+    }
+
+    public MockImeSessionRule(boolean ignoreInitException) {
+        this(InstrumentationRegistry.getTargetContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder(), ignoreInitException);
+    }
+
+    public MockImeSessionRule(@NonNull Context context, @NonNull UiAutomation uiAutomation,
+            @NonNull ImeSettings.Builder imeSettings, boolean ignoreInitException) {
+        mContext = context;
+        mUiAutomation = uiAutomation;
+        mImeSettings = imeSettings;
+        mIgnoreInitException = ignoreInitException;
+    }
+
+    @Override
+    public Statement apply(@NonNull Statement base, @NonNull Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "Creating MockImeSession on " + description.getDisplayName());
+                }
+                try {
+                    mMockImeSession = MockImeSession.create(mContext, mUiAutomation, mImeSettings);
+                } catch (Throwable t) {
+                    Log.e(TAG, "Error creating MockImeSession", t);
+                    if (mIgnoreInitException) {
+                        mInitException = t;
+                    } else {
+                        throw t;
+                    }
+                }
+                try {
+                    base.evaluate();
+                } finally {
+                    if (mMockImeSession != null) {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "Closing MockImeSession on " + description.getDisplayName());
+                        }
+                        mMockImeSession.close();
+                    }
+                }
+            }
+        };
+    }
+
+    @Nullable
+    public MockImeSession getMockImeSession() {
+        return mMockImeSession;
+    }
+
+    @Nullable
+    public Throwable getInitException() {
+        return mInitException;
+    }
+
+    public void assumeAvailable() {
+        if (mInitException == null) return;
+        assumeTrue("Exception setting MockingIme: " + mInitException, mInitException == null);
+    }
+}
diff --git a/tests/autofillservice/src/com/android/cts/mockime/SettingsProvider.java b/tests/autofillservice/src/com/android/cts/mockime/SettingsProvider.java
new file mode 100644
index 0000000..d9a565b
--- /dev/null
+++ b/tests/autofillservice/src/com/android/cts/mockime/SettingsProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.mockime;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+/**
+ * {@link ContentProvider} to receive {@link ImeSettings} via
+ * {@link ContentProvider#call(String, String, String, Bundle)}.
+ */
+public class SettingsProvider extends ContentProvider {
+
+    static final Uri AUTHORITY = Uri.parse("content://com.android.cts.mockime.provider");
+
+    @Nullable
+    private static ImeSettings sSettings = null;
+
+    @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 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;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        if ("write".equals(method)) {
+            sSettings = null;
+            final String callingPackageName = getCallingPackage();
+            if (callingPackageName == null) {
+                throw new SecurityException("Failed to obtain the calling package name.");
+            }
+            sSettings = new ImeSettings(callingPackageName, extras);
+        } else if ("delete".equals(method)) {
+            sSettings = null;
+        }
+        return Bundle.EMPTY;
+    }
+
+    static ImeSettings getSettings() {
+        return sSettings;
+    }
+}
diff --git a/tests/core/runner-axt/src/com/android/cts/runner/CrashParserRunListener.java b/tests/core/runner-axt/src/com/android/cts/runner/CrashParserRunListener.java
new file mode 100644
index 0000000..d838fb4
--- /dev/null
+++ b/tests/core/runner-axt/src/com/android/cts/runner/CrashParserRunListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.runner;
+
+import androidx.test.internal.runner.listener.InstrumentationRunListener;
+import android.util.Log;
+import org.junit.runner.Description;
+
+/**
+ * A {@link RunListener} for CrashParser. Dumps the test name to logs when
+ * tests start.
+ */
+public class CrashParserRunListener extends InstrumentationRunListener {
+
+    private static final String TAG = "CrashParserRunListener";
+
+    // Constant must be kept in sync with CrashUtils.java
+    public static final String NEW_TEST_ALERT = "New test starting with name: ";
+
+    @Override
+    public void testStarted(Description description) throws Exception {
+        Log.i(TAG, NEW_TEST_ALERT + description.toString());
+    }
+
+}
diff --git a/tests/core/runner/src/com/android/cts/runner/CrashParserRunListener.java b/tests/core/runner/src/com/android/cts/runner/CrashParserRunListener.java
new file mode 100644
index 0000000..fbbb684
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/runner/CrashParserRunListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.runner;
+
+import android.support.test.internal.runner.listener.InstrumentationRunListener;
+import android.util.Log;
+import org.junit.runner.Description;
+
+/**
+ * A {@link RunListener} for CrashParser. Dumps the test name to logs when
+ * tests start.
+ */
+public class CrashParserRunListener extends InstrumentationRunListener {
+
+    private static final String TAG = "CrashParserRunListener";
+
+    // Constant must be kept in sync with CrashUtils.java
+    public static final String NEW_TEST_ALERT = "New test starting with name: ";
+
+    @Override
+    public void testStarted(Description description) throws Exception {
+        Log.i(TAG, NEW_TEST_ALERT + description.toString());
+    }
+
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
index 0e92146..712f729 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
@@ -346,6 +346,7 @@
         getLaunchActivityBuilder().execute();
 
         waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
+        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_STOP));
 
         // Finish top activity and verify that activity below became focused.
         getLifecycleLog().clear();
diff --git a/tests/security/src/android/keystore/cts/AuthorizationList.java b/tests/security/src/android/keystore/cts/AuthorizationList.java
index e4c5eb6..e6849f3 100644
--- a/tests/security/src/android/keystore/cts/AuthorizationList.java
+++ b/tests/security/src/android/keystore/cts/AuthorizationList.java
@@ -185,6 +185,7 @@
     private Date creationDateTime;
     private Integer origin;
     private boolean rollbackResistant;
+    private boolean rollbackResistance;
     private RootOfTrust rootOfTrust;
     private Integer osVersion;
     private Integer osPatchLevel;
@@ -270,6 +271,9 @@
                 case KM_TAG_ROLLBACK_RESISTANT & KEYMASTER_TAG_TYPE_MASK:
                     rollbackResistant = true;
                     break;
+                case KM_TAG_ROLLBACK_RESISTANCE & KEYMASTER_TAG_TYPE_MASK:
+                    rollbackResistance = true;
+                    break;
                 case KM_TAG_AUTH_TIMEOUT & KEYMASTER_TAG_TYPE_MASK:
                     authTimeout = Asn1Utils.getIntegerFromAsn1(value);
                     break;
@@ -535,6 +539,10 @@
         return rollbackResistant;
     }
 
+    public boolean isRollbackResistance() {
+        return rollbackResistance;
+    }
+
     public RootOfTrust getRootOfTrust() {
         return rootOfTrust;
     }
@@ -675,6 +683,10 @@
             s.append("\nRollback resistant: true");
         }
 
+        if (rollbackResistance) {
+            s.append("\nRollback resistance: true");
+        }
+
         if (rootOfTrust != null) {
             s.append("\nRoot of Trust:\n");
             s.append(rootOfTrust);
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index 7ded622..064bf0b 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -33,6 +33,7 @@
 import android.telecom.TelecomManager;
 import android.test.AndroidTestCase;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.FeatureUtil;
 
 import java.util.List;
@@ -74,6 +75,7 @@
      * Test ACTION_VIEW when url is http://web_address,
      * it will open a browser window to the URL specified.
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testViewNormalUrl() {
         Uri uri = Uri.parse(NORMAL_URL);
         Intent intent = new Intent(Intent.ACTION_VIEW, uri);
@@ -84,6 +86,7 @@
      * Test ACTION_VIEW when url is https://web_address,
      * it will open a browser window to the URL specified.
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testViewSecureUrl() {
         Uri uri = Uri.parse(SECURE_URL);
         Intent intent = new Intent(Intent.ACTION_VIEW, uri);
@@ -94,6 +97,7 @@
      * Test ACTION_WEB_SEARCH when url is http://web_address,
      * it will open a browser window to the URL specified.
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testWebSearchNormalUrl() {
         Uri uri = Uri.parse(NORMAL_URL);
         Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
@@ -105,6 +109,7 @@
      * Test ACTION_WEB_SEARCH when url is https://web_address,
      * it will open a browser window to the URL specified.
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testWebSearchSecureUrl() {
         Uri uri = Uri.parse(SECURE_URL);
         Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
@@ -184,6 +189,7 @@
     /**
      * Test ACTION_SHOW_CALL_SETTINGS, it will display the call preferences.
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testShowCallSettings() {
         PackageManager packageManager = mContext.getPackageManager();
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
@@ -195,6 +201,7 @@
     /**
      * Test ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS, it will display the respond by SMS preferences.
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testShowRespondViaSmsSettings() {
         PackageManager packageManager = mContext.getPackageManager();
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
@@ -206,6 +213,7 @@
     /**
      * Test start camera by intent
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testCamera() {
         PackageManager packageManager = mContext.getPackageManager();
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
@@ -224,6 +232,7 @@
         }
     }
 
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testSettings() {
         assertCanBeHandled(new Intent(Settings.ACTION_SETTINGS));
     }
@@ -231,6 +240,7 @@
     /**
      * Test add event in calendar
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testCalendarAddAppointment() {
         Intent addAppointmentIntent = new Intent(Intent.ACTION_EDIT);
         addAppointmentIntent.setType("vnd.android.cursor.item/event");
@@ -240,6 +250,7 @@
     /**
      * Test view call logs
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testContactsCallLogs() {
         PackageManager packageManager = mContext.getPackageManager();
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
@@ -252,6 +263,7 @@
     /**
      * Test view music playback
      */
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testMusicPlayback() {
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setDataAndType(ContentUris.withAppendedId(
@@ -259,6 +271,7 @@
         assertCanBeHandled(intent);
     }
 
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testAlarmClockSetAlarm() {
         Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM);
         intent.putExtra(AlarmClock.EXTRA_MESSAGE, "Custom message");
@@ -267,17 +280,20 @@
         assertCanBeHandled(intent);
     }
 
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testAlarmClockSetTimer() {
         Intent intent = new Intent(AlarmClock.ACTION_SET_TIMER);
         intent.putExtra(AlarmClock.EXTRA_LENGTH, 60000);
         assertCanBeHandled(intent);
     }
 
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testAlarmClockShowAlarms() {
         Intent intent = new Intent(AlarmClock.ACTION_SHOW_ALARMS);
         assertCanBeHandled(intent);
     }
 
+    @CddTest(requirement="3.2.3.1/C-0-1")
     public void testAlarmClockShowTimers() {
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)) {
             return;
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderTest.java b/tests/tests/content/src/android/content/cts/ContentProviderTest.java
index dc4a031..95f4611 100644
--- a/tests/tests/content/src/android/content/cts/ContentProviderTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentProviderTest.java
@@ -29,6 +29,8 @@
 
 import android.content.cts.R;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -36,6 +38,7 @@
 /**
  * Test {@link ContentProvider}.
  */
+@CddTest(requirement="3.5/C-0-2")
 public class ContentProviderTest extends AndroidTestCase {
     private static final String TEST_PACKAGE_NAME = "android.content.cts";
     private static final String TEST_FILE_NAME = "testFile.tmp";
diff --git a/tests/tests/content/src/android/content/cts/IntentTest.java b/tests/tests/content/src/android/content/cts/IntentTest.java
index af53e56..51684ae 100644
--- a/tests/tests/content/src/android/content/cts/IntentTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentTest.java
@@ -39,6 +39,7 @@
 import android.util.Xml;
 
 import com.android.content.cts.DummyParcelable;
+import com.android.compatibility.common.util.CddTest;
 
 import java.io.IOException;
 import java.io.Serializable;
@@ -47,6 +48,7 @@
 import java.util.Objects;
 import java.util.Set;
 
+@CddTest(requirement="3.5/C-0-1")
 public class IntentTest extends AndroidTestCase {
 
     private Intent mIntent;
diff --git a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
index 7ac5246..8fdc701 100644
--- a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
+++ b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
@@ -83,8 +83,8 @@
         allowedDensities.add(DisplayMetrics.DENSITY_XXHIGH);
         allowedDensities.add(DisplayMetrics.DENSITY_560);
         allowedDensities.add(DisplayMetrics.DENSITY_XXXHIGH);
-        assertTrue("DisplayMetrics#densityDpi must be one of the DisplayMetrics.DENSITY_* values: "
-                + allowedDensities, allowedDensities.contains(mMetrics.densityDpi));
+        assertTrue("DisplayMetrics.DENSITY_DEVICE_STABLE must be one of the DisplayMetrics.DENSITY_* values: "
+                + allowedDensities, allowedDensities.contains(DisplayMetrics.DENSITY_DEVICE_STABLE));
 
         assertEquals(mMetrics.density,
                 (float) mMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT,
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 669a65a..342cba7 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -22,6 +22,7 @@
 import android.graphics.Rect;
 import android.hardware.Camera;
 import android.media.AudioManager;
+import android.media.CamcorderProfile;
 import android.media.MediaDataSource;
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
@@ -58,6 +59,7 @@
 import java.io.File;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.StringTokenizer;
 import java.util.UUID;
@@ -1024,7 +1026,14 @@
         // getSupportedVideoSizes returns null when separate video/preview size
         // is not supported.
         if (videoSizes == null) {
-            videoSizes = parameters.getSupportedPreviewSizes();
+            // If we have CamcorderProfile use it instead of Preview size.
+            if (CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_LOW)) {
+                CamcorderProfile profile = CamcorderProfile.get(0, CamcorderProfile.QUALITY_LOW);
+                videoSizes = new ArrayList();
+                videoSizes.add(mCamera.new Size(profile.videoFrameWidth, profile.videoFrameHeight));
+            } else {
+                videoSizes = parameters.getSupportedPreviewSizes();
+            }
         }
         for (Camera.Size size : videoSizes)
         {
diff --git a/tests/tests/media/src/android/media/cts/RoutingTest.java b/tests/tests/media/src/android/media/cts/RoutingTest.java
index e1765d8..f2e078a 100644
--- a/tests/tests/media/src/android/media/cts/RoutingTest.java
+++ b/tests/tests/media/src/android/media/cts/RoutingTest.java
@@ -666,6 +666,12 @@
             return;
         }
 
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        if (devices.length < 2) {
+            // In this case, we cannot switch output device, that may cause the test fail.
+            return;
+        }
+
         mRoutingChanged = false;
         mRoutingChangedLooper = null;
         // Create MediaPlayer in another thread to make sure there is a looper active for events.
@@ -678,6 +684,27 @@
                 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);
+                                break;
+                            }
+                        }
+                    }
+                }, 1000);
                 Looper.loop();
                 mediaPlayer.removeOnRoutingChangedListener(listener);
                 mediaPlayer.stop();
diff --git a/tests/tests/ndef/Android.mk b/tests/tests/ndef/Android.mk
index df28c7b..0e5b1dd 100644
--- a/tests/tests/ndef/Android.mk
+++ b/tests/tests/ndef/Android.mk
@@ -24,7 +24,7 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt compatibility-device-util-axt
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/ndef/src/android/ndef/cts/NdefTest.java b/tests/tests/ndef/src/android/ndef/cts/NdefTest.java
index b0a838c..7b8cadb 100644
--- a/tests/tests/ndef/src/android/ndef/cts/NdefTest.java
+++ b/tests/tests/ndef/src/android/ndef/cts/NdefTest.java
@@ -24,6 +24,8 @@
 import android.nfc.NdefRecord;
 import android.nfc.FormatException;
 
+import com.android.compatibility.common.util.CddTest;
+
 import junit.framework.TestCase;
 
 /**
@@ -32,6 +34,7 @@
  * hardware is required, so these API's are mandatory even on Android
  * devices without NFC hardware.
  */
+@CddTest(requirement="7.4.4/C-0-1")
 public class NdefTest extends TestCase {
     static final Charset ASCII = Charset.forName("US-ASCII");
     static final Charset UTF8 = Charset.forName("UTF-8");
@@ -86,6 +89,7 @@
                 new byte[] {1,2,3}, new byte[] {4,5,6}, new byte[] {7,8,9})).hashCode());
     }
 
+    @CddTest(requirement="7.4.4/C-0-1")
     public void testInvalidParsing() throws FormatException {
         final byte[][] invalidNdefMessages = {
             {},                                    // too short
@@ -115,6 +119,7 @@
         }
     }
 
+    @CddTest(requirement="7.4.4/C-0-1")
     public void testValidParsing() throws FormatException {
         // short record
         assertEquals(new NdefMessage(new NdefRecord(NdefRecord.TNF_EMPTY, null, null, null)),
@@ -307,6 +312,7 @@
                 (byte) 0x6f, (byte) 0x6d}));
     }
 
+    @CddTest(requirement="7.4.4/C-0-1")
     public void testCreateUri() {
         assertEquals(new byte[] {
                 (byte)0xD1, 1, 8, 'U', (byte)0x01, 'n', 'f', 'c', '.', 'c', 'o', 'm'},
@@ -327,6 +333,7 @@
                 new NdefMessage(NdefRecord.createUri("\u00A2")).toByteArray());
     }
 
+    @CddTest(requirement="7.4.4/C-0-1")
     public void testCreateMime() {
         assertEquals(
                 new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "text/plain".getBytes(ASCII), null,
@@ -385,6 +392,7 @@
                 NdefRecord.createExternal("A.b", "C!", null));
     }
 
+    @CddTest(requirement="7.4.4/C-0-1")
     public void testCreateApplicationRecord() throws FormatException {
         NdefMessage m;
         NdefRecord r;
@@ -431,6 +439,7 @@
         assertEquals("com.foo.bar".getBytes(), r.getPayload());
     }
 
+    @CddTest(requirement="7.4.4/C-0-1")
     public void testToByteArray() throws FormatException {
         NdefRecord r;
 
@@ -480,6 +489,7 @@
                 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,})).toByteArray());
     }
 
+    @CddTest(requirement="7.4.4/C-0-1")
     public void testToUri() {
         // absolute uri
         assertEquals(Uri.parse("http://www.android.com"),
@@ -516,6 +526,7 @@
         assertEquals(null, new NdefRecord(NdefRecord.TNF_EMPTY, null, null, null).toUri());
     }
 
+    @CddTest(requirement="7.4.4/C-0-1")
     public void testToMimeType() {
         assertEquals(null, NdefRecord.createUri("http://www.android.com").toMimeType());
         assertEquals(null, new NdefRecord(NdefRecord.TNF_EMPTY, null, null, null).toMimeType());
diff --git a/tests/tests/net/jni/NativeDnsJni.c b/tests/tests/net/jni/NativeDnsJni.c
index 352c0c5..6d3d1c3 100644
--- a/tests/tests/net/jni/NativeDnsJni.c
+++ b/tests/tests/net/jni/NativeDnsJni.c
@@ -120,8 +120,8 @@
             gai_strerror(res));
         return JNI_FALSE;
     }
-    if (strstr(buf, "google.com") == NULL) {
-        ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com: %s",
+    if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
+        ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
             GoogleDNSIpV4Address, buf);
         return JNI_FALSE;
     }
@@ -133,8 +133,9 @@
             res, gai_strerror(res));
         return JNI_FALSE;
     }
-    if (strstr(buf, "google.com") == NULL) {
-        ALOGD("getnameinfo(%s) didn't return google.com: %s", GoogleDNSIpV6Address2, buf);
+    if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
+        ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
+            GoogleDNSIpV6Address2, buf);
         return JNI_FALSE;
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/BlockedNumberContractTest.java b/tests/tests/provider/src/android/provider/cts/BlockedNumberContractTest.java
index 756fa76..87e8b3e 100644
--- a/tests/tests/provider/src/android/provider/cts/BlockedNumberContractTest.java
+++ b/tests/tests/provider/src/android/provider/cts/BlockedNumberContractTest.java
@@ -30,6 +30,8 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.android.compatibility.common.util.CddTest;
+
 import junit.framework.Assert;
 
 import java.util.ArrayList;
@@ -43,6 +45,7 @@
 // make cts
 // cts-tradefed
 // run cts -m CtsProviderTestCases --test android.provider.cts.BlockedNumberContractTest
+@CddTest(requirement="7.4.1.1/C-1-1,C-1-2")
 public class BlockedNumberContractTest extends TestCaseThatRunsIfTelephonyIsEnabled {
     private static final String TAG = "BlockedNumberContractTest";
     private ContentResolver mContentResolver;
@@ -74,6 +77,7 @@
         super.tearDown();
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-2")
     public void testProviderInteractionsAsRegularApp_fails() {
         if (!mIsSystemUser) {
             Log.i(TAG, "skipping BlockedNumberContractTest");
@@ -131,6 +135,7 @@
         assertNull(mContentResolver.getType(BlockedNumberContract.AUTHORITY_URI));
     }
 
+    @CddTest(requirement="7.4.1.1/CC-1-2")
     public void testInsertAndBlockCheck_succeeds() throws Exception {
         if (!mIsSystemUser) {
             Log.i(TAG, "skipping BlockedNumberContractTest");
@@ -162,6 +167,7 @@
         assertFalse(BlockedNumberContract.isBlocked(mContext, "random string"));
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-2")
     public void testUnblock_succeeds() throws Exception {
         if (!mIsSystemUser) {
             Log.i(TAG, "skipping BlockedNumberContractTest");
@@ -181,6 +187,7 @@
         assertFalse(BlockedNumberContract.isBlocked(mContext, "1234@abcd.com"));
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-2")
     public void testInsert_failsWithInvalidInputs() throws Exception {
         if (!mIsSystemUser) {
             Log.i(TAG, "skipping BlockedNumberContractTest");
@@ -226,6 +233,7 @@
         }
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-2")
     public void testUpdate_isUnsupported() throws  Exception {
         if (!mIsSystemUser) {
             Log.i(TAG, "skipping BlockedNumberContractTest");
@@ -250,6 +258,7 @@
         assertFalse(BlockedNumberContract.isBlocked(mContext, ""));
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-2")
     public void testDelete() throws Exception {
         if (!mIsSystemUser) {
             Log.i(TAG, "skipping BlockedNumberContractTest");
@@ -321,6 +330,7 @@
         }
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-2")
     public void testProviderNotifiesChangesUsingContentObserver() throws Exception {
         if (!mIsSystemUser) {
             Log.i(TAG, "skipping BlockedNumberContractTest");
@@ -351,6 +361,7 @@
         }
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-2")
     public void testAccessingNonExistentMethod_fails() throws Exception {
         if (!mIsSystemUser) {
             Log.i(TAG, "skipping BlockedNumberContractTest");
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 18159e3..7103969 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -57,6 +57,8 @@
                      android:label="CTS tests of android.security.cts">
         <meta-data android:name="listener"
             android:value="com.android.cts.runner.CtsTestRunListener" />
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CrashParserRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index 5482dd6..3a879d1 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -20,6 +20,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSecurityTestCases.apk" />
     </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.CrashReporter" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.security.cts" />
         <option name="runtime-hint" value="1h8m15s" />
diff --git a/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java b/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java
index 8f6477e..5002a72 100644
--- a/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java
+++ b/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java
@@ -98,7 +98,7 @@
   public SocketChannel socketChannel;
   public SSLEngine clientEngine;
   public String remoteAddress = "127.0.0.1";
-  public int port = 9000;
+  public int port = 7000;
   public ByteBuffer[] dataOutAppBuffers = new ByteBuffer[3];
   public ByteBuffer dataOutNetBuffer;
   public ByteBuffer hsInAppBuffer, hsInNetBuffer, hsOutAppBuffer, hsOutNetBuffer;
@@ -492,7 +492,7 @@
   public ByteBuffer dataInAppBuffer, dataInNetBuffer;
 
   final String hostAddress = "127.0.0.1";
-  public int port = 9000;
+  public int port = 7000;
   public boolean bActive = false;
 
   public Selector selector;
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 31769e1..adf8131 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -47,8 +47,13 @@
 import android.view.Surface;
 import android.webkit.cts.CtsTestServer;
 
+import com.android.compatibility.common.util.CrashUtils;
+
 import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
@@ -58,6 +63,10 @@
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
 
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import android.security.cts.R;
 
 
@@ -70,6 +79,7 @@
     static final String TAG = "StagefrightTest";
 
     private final long TIMEOUT_NS = 10000000000L;  // 10 seconds.
+    private final static long CHECK_INTERVAL = 50;
 
     public StagefrightTest() {
     }
@@ -898,10 +908,41 @@
         return new Surface(surfaceTex);
     }
 
+    public JSONArray getCrashReport(String testname, long timeout)
+        throws InterruptedException {
+        Log.i(TAG, CrashUtils.UPLOAD_REQUEST);
+        File reportFile = new File(CrashUtils.DEVICE_PATH, testname);
+        File lockFile = new File(CrashUtils.DEVICE_PATH, CrashUtils.LOCK_FILENAME);
+        while ((!reportFile.exists() || !lockFile.exists()) && timeout > 0) {
+            Thread.sleep(CHECK_INTERVAL);
+            timeout -= CHECK_INTERVAL;
+        }
+        if (!reportFile.exists() || !reportFile.isFile() || !lockFile.exists()) {
+            return null;
+        }
+        try (BufferedReader reader = new BufferedReader(new FileReader(reportFile))) {
+            StringBuilder json = new StringBuilder();
+            String line = reader.readLine();
+            while (line != null) {
+                json.append(line);
+                line = reader.readLine();
+            }
+            return new JSONArray(json.toString());
+        } catch (IOException | JSONException e) {
+            Log.e(TAG, "Failed to deserialize crash list with error " + e.getMessage());
+            return null;
+        }
+    }
+
     class MediaPlayerCrashListener
-    implements MediaPlayer.OnErrorListener,
+        implements MediaPlayer.OnErrorListener,
         MediaPlayer.OnPreparedListener,
         MediaPlayer.OnCompletionListener {
+
+        private final String[] validProcessNames = {
+            "mediaserver", "mediadrmserver", "media.extractor", "media.codec", "media.metrics"
+        };
+
         @Override
         public boolean onError(MediaPlayer mp, int newWhat, int extra) {
             Log.i(TAG, "error: " + newWhat + "/" + extra);
@@ -942,6 +983,21 @@
                 // and see if more errors show up.
                 SystemClock.sleep(1000);
             }
+            if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
+                JSONArray crashes = getCrashReport(getName(), 5000);
+                if (crashes == null) {
+                    Log.e(TAG, "Crash results not found for test " + getName());
+                    return what;
+                } else if (CrashUtils.detectCrash(validProcessNames, true, crashes)) {
+                    return what;
+                } else {
+                    Log.i(TAG, "Crash ignored due to no security crash found for test " +
+                        getName());
+                    // 0 is the code for no error.
+                    return 0;
+                }
+
+            }
             return what;
         }
 
diff --git a/tests/tests/systemintents/Android.mk b/tests/tests/systemintents/Android.mk
index c68aaf1..16b8998 100644
--- a/tests/tests/systemintents/Android.mk
+++ b/tests/tests/systemintents/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_PACKAGE_NAME := CtsSystemIntentTestCases
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt androidx.test.rules
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt androidx.test.rules compatibility-device-util-axt
 
 LOCAL_SDK_VERSION := test_current
 
diff --git a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
index f6911b3..941fef7 100644
--- a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
+++ b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
@@ -29,6 +29,8 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.CddTest;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -81,6 +83,7 @@
                     new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS))
     };
 
+    @CddTest(requirement="3.8.3.3/C-1-1,3.2.3.5/C-1-1,7.4.7/C-1-2,C-2-3,6.2/C-0-1")
     @Test
     public void testSystemIntents() {
         final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
diff --git a/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
index 954112b..2d60870 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
@@ -34,6 +34,8 @@
 import android.telecom.VideoProfile;
 import android.telephony.TelephonyManager;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.util.List;
 
 /**
@@ -374,6 +376,7 @@
         }
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-3")
     public void testIncomingCallFromBlockedNumber_IsRejected() throws Exception {
         if (!mShouldTestTelecom) {
             return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/TelecomAvailabilityTest.java b/tests/tests/telecom/src/android/telecom/cts/TelecomAvailabilityTest.java
index 8163520..fba31a0 100644
--- a/tests/tests/telecom/src/android/telecom/cts/TelecomAvailabilityTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/TelecomAvailabilityTest.java
@@ -29,6 +29,8 @@
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -99,6 +101,7 @@
                 telephonyMatches);
     }
 
+    @CddTest(requirement="7.4.1.1/C-1-6")
     public void testTelecomCanManageBlockedNumbers() {
         if (!shouldTestTelecom(mContext)) {
             return;
diff --git a/tests/tests/view/res/layout-land/drag_drop_layout.xml b/tests/tests/view/res/layout-land/drag_drop_layout.xml
new file mode 100644
index 0000000..79aa5df
--- /dev/null
+++ b/tests/tests/view/res/layout-land/drag_drop_layout.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<LinearLayout
+        android:id="@+id/drag_drop_activity_main"
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+    <FrameLayout
+            android:id="@+id/container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="42dp"
+            android:background="#BBBBBB">
+        <FrameLayout
+                android:id="@+id/subcontainer"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="42dp"
+                android:background="#666666">
+            <View
+                    android:id="@+id/inner"
+                    android:layout_width="42dp"
+                    android:layout_height="42dp"
+                    android:layout_margin="42dp"
+                    android:background="#00FF00" />
+        </FrameLayout>
+    </FrameLayout>
+    <View
+            android:id="@+id/draggable"
+            android:layout_width="42dp"
+            android:layout_height="42dp"
+            android:layout_margin="42dp"
+            android:background="#0000FF" />
+</LinearLayout>
diff --git a/tests/tests/view/res/layout/drag_drop_layout.xml b/tests/tests/view/res/layout/drag_drop_layout.xml
index cf882bd..9f4614c 100644
--- a/tests/tests/view/res/layout/drag_drop_layout.xml
+++ b/tests/tests/view/res/layout/drag_drop_layout.xml
@@ -25,26 +25,26 @@
             android:id="@+id/container"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_margin="42dp"
+            android:layout_margin="21dp"
             android:background="#BBBBBB">
         <FrameLayout
                 android:id="@+id/subcontainer"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_margin="42dp"
+                android:layout_margin="21dp"
                 android:background="#666666">
             <View
                     android:id="@+id/inner"
-                    android:layout_width="42dp"
-                    android:layout_height="42dp"
-                    android:layout_margin="42dp"
+                    android:layout_width="21dp"
+                    android:layout_height="21dp"
+                    android:layout_margin="21dp"
                     android:background="#00FF00" />
         </FrameLayout>
     </FrameLayout>
     <View
             android:id="@+id/draggable"
-            android:layout_width="42dp"
-            android:layout_height="42dp"
-            android:layout_margin="42dp"
+            android:layout_width="21dp"
+            android:layout_height="21dp"
+            android:layout_margin="21dp"
             android:background="#0000FF" />
 </LinearLayout>
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index f5f3139..14dd431 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -1781,10 +1781,30 @@
                     && mOnUiThread.getHeight() != 0;
             }
         }.run();
-        assertEquals(mOnUiThread.getHeight(),
-                mOnUiThread.getContentHeight() * mOnUiThread.getScale(), 2f);
 
-        final int pageHeight = 600;
+        final int tolerance = 2;
+        // getHeight() returns physical pixels and it is from web contents' size, getContentHeight()
+        // returns CSS pixels and it is from compositor. In order to compare these two values, we
+        // need to scale getContentHeight() by the device scale factor. This also amplifies any
+        // rounding errors. Internally, getHeight() could also have rounding error first and then
+        // times device scale factor, so we are comparing two rounded numbers below.
+        // We allow 2 * getScale() as the delta, because getHeight() and getContentHeight() may
+        // use different rounding algorithms and the results are from different computation
+        // sequences. The extreme case is that in CSS pixel we have 2 as differences (0.9999 rounded
+        // down and 1.0001 rounded up), therefore we ended with 2 * getScale().
+        assertEquals(
+                mOnUiThread.getHeight(),
+                mOnUiThread.getContentHeight() * mOnUiThread.getScale(),
+                tolerance * mOnUiThread.getScale());
+
+        // Make pageHeight bigger than the larger dimension of the device, so the page is taller
+        // than viewport. Because when layout_height set to match_parent, getContentHeight() will
+        // give maximum value between the actual web content height and the viewport height. When
+        // viewport height is bigger, |extraSpace| below is not the extra space on the web page.
+        // Note that we are passing physical pixels rather than CSS pixels here, since screen
+        // density scale is generally greater than 1, it only makes the page content taller.
+        DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
+        final int pageHeight = Math.max(metrics.widthPixels, metrics.heightPixels);
         // set the margin to 0
         final String p = "<p style=\"height:" + pageHeight
                 + "px;margin:0px auto;\">Get the height of HTML content.</p>";
@@ -1803,7 +1823,13 @@
         new PollingCheck() {
             @Override
             protected boolean check() {
-                return pageHeight + pageHeight + extraSpace == mOnUiThread.getContentHeight();
+                // |pageHeight| is accurate, |extraSpace| = getContentheight() - |pageHeight|, so it
+                // might have rounding error +-1, also getContentHeight() might have rounding error
+                // +-1, so we allow error 2. Note that |pageHeight|, |extraSpace| and
+                // getContentHeight() are all CSS pixels.
+                final int expectedContentHeight = pageHeight + pageHeight + extraSpace;
+                return Math.abs(expectedContentHeight - mOnUiThread.getContentHeight())
+                        <= tolerance;
             }
         }.run();
     }
diff --git a/tests/tests/widget/src/android/widget/cts/ZoomButtonTest.java b/tests/tests/widget/src/android/widget/cts/ZoomButtonTest.java
index acd0c4c..54d7c22 100644
--- a/tests/tests/widget/src/android/widget/cts/ZoomButtonTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ZoomButtonTest.java
@@ -44,10 +44,6 @@
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ZoomButtonTest {
@@ -128,87 +124,62 @@
         assertFalse(mZoomButton.dispatchUnhandledMove(null, View.FOCUS_DOWN));
     }
 
-    private void verifyZoomSpeed(ZoomClickListener zoomClickListener, long zoomSpeedMs) {
-        mZoomButton.setZoomSpeed(zoomSpeedMs);
-
-        final long startTime = System.nanoTime();
-        // Emulate long click that "lasts" for ten seconds
-        CtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mZoomButton, 10000);
-
-        final List<Long> callbackInvocations = zoomClickListener.getClickTimes();
-        assertFalse("Expecting at least one callback", callbackInvocations.isEmpty());
-
-        // Verify that the first callback is fired after the system-level long press timeout.
-        final long minTimeUntilFirstInvocationMs = ViewConfiguration.getLongPressTimeout();
-        final long actualTimeUntilFirstInvocationNs = callbackInvocations.get(0) - startTime;
-        assertTrue("First callback not during long press timeout was " +
-                        actualTimeUntilFirstInvocationNs / NANOS_IN_MILLI +
-                        " while long press timeout is " + minTimeUntilFirstInvocationMs,
-                (callbackInvocations.get(0) - startTime) >
-                        minTimeUntilFirstInvocationMs * NANOS_IN_MILLI);
-
-        // Verify that subsequent callbacks are at least zoom-speed milliseconds apart. Note that
-        // we do not have any hard guarantee about the max limit on the time between successive
-        // callbacks.
-        final long minTimeBetweenInvocationsNs = zoomSpeedMs * NANOS_IN_MILLI;
-        if (callbackInvocations.size() > 1) {
-            for (int i = 0; i < callbackInvocations.size() - 1; i++) {
-                final long actualTimeBetweenInvocationsNs =
-                        (callbackInvocations.get(i + 1) - callbackInvocations.get(i)) *
-                                NANOS_IN_MILLI;
-                assertTrue("Callback " + (i + 1) + " happened " +
-                                actualTimeBetweenInvocationsNs / NANOS_IN_MILLI +
-                                " after the previous one, while zoom speed is " + zoomSpeedMs,
-                        actualTimeBetweenInvocationsNs > minTimeBetweenInvocationsNs);
-            }
-        }
-    }
-
-    @LargeTest
-    @Test
-    public void testOnLongClick() {
-        // Since Mockito doesn't have utilities to track the timestamps of method invocations,
-        // we're using our own custom click listener for that. We want to verify that the
-        // first listener invocation was after long press timeout, and the rest were spaced
-        // by at least our zoom speed milliseconds
-
-        mZoomButton.setEnabled(true);
-        ZoomClickListener zoomClickListener = new ZoomClickListener();
-        mZoomButton.setOnClickListener(zoomClickListener);
-
-        verifyZoomSpeed(zoomClickListener, 2000);
-    }
-
     @LargeTest
     @Test
     public void testSetZoomSpeed() {
-        final long[] zoomSpeeds = { 100, -1, 5000, 1000, 2500 };
+        final long[] zoomSpeeds = { 0, 100 };
         mZoomButton.setEnabled(true);
         ZoomClickListener zoomClickListener = new ZoomClickListener();
         mZoomButton.setOnClickListener(zoomClickListener);
 
         for (long zoomSpeed : zoomSpeeds) {
-            // Reset the tracker list of our listener, but continue using it for testing
+            // Reset the tracking state of our listener, but continue using it for testing
             // various zoom speeds on the same ZoomButton
             zoomClickListener.reset();
-            verifyZoomSpeed(zoomClickListener, zoomSpeed);
+
+            mZoomButton.setZoomSpeed(zoomSpeed);
+
+            final long startTime = System.nanoTime();
+            // Emulate long click
+            long longPressWait = ViewConfiguration.getLongPressTimeout()
+                    + zoomSpeed + 100;
+            CtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mZoomButton,
+                    longPressWait);
+
+            final Long callbackFirstInvocationTime = zoomClickListener.getTimeOfFirstClick();
+            assertNotNull("Expecting at least one callback", callbackFirstInvocationTime);
+
+            // Verify that the first callback is fired after the system-level long press timeout.
+            final long minTimeUntilFirstInvocationMs = ViewConfiguration.getLongPressTimeout();
+            final long actualTimeUntilFirstInvocationNs = callbackFirstInvocationTime - startTime;
+            assertTrue("First callback not during long press timeout was "
+                            + actualTimeUntilFirstInvocationNs / NANOS_IN_MILLI
+                            + " while long press timeout is " + minTimeUntilFirstInvocationMs,
+                    (callbackFirstInvocationTime - startTime)
+                            > minTimeUntilFirstInvocationMs * NANOS_IN_MILLI);
+            assertTrue("First callback should have happened sooner than "
+                            + actualTimeUntilFirstInvocationNs / NANOS_IN_MILLI,
+                    (callbackFirstInvocationTime - startTime)
+                            <= (minTimeUntilFirstInvocationMs + 100) * NANOS_IN_MILLI);
         }
     }
 
     private static class ZoomClickListener implements View.OnClickListener {
-        private List<Long> mClickTimes = new ArrayList<>();
+        private Long mTimeOfFirstClick = null;
 
         public void reset() {
-            mClickTimes.clear();
+            mTimeOfFirstClick = null;
         }
 
-        public List<Long> getClickTimes() {
-            return Collections.unmodifiableList(mClickTimes);
+        public Long getTimeOfFirstClick() {
+            return mTimeOfFirstClick;
         }
 
         public void onClick(View v) {
-            // Add the current system time to the tracker list
-            mClickTimes.add(System.nanoTime());
+            if (mTimeOfFirstClick == null) {
+                // Mark the current system time as the time of first click
+                mTimeOfFirstClick = System.nanoTime();
+            }
         }
     }
 }
diff --git a/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java b/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java
index e9c7ae0..e18df4c 100644
--- a/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java
+++ b/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java
@@ -86,6 +86,11 @@
         final int TRANSACTION_SIZE = 1000;
         double[] applyBatchTimes = MeasureTime.measure(TRANSACTION_RUNS, new MeasureRun() {
             @Override
+            public void prepare(int i) {
+                mContentResolver.delete(Channels.CONTENT_URI, null, null);
+            }
+
+            @Override
             public void run(int i) {
                 operations.clear();
                 for (int j = 0; j < TRANSACTION_SIZE; ++j) {