Merge "Test behavior when declaring duplicate permissions"
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java
new file mode 100644
index 0000000..eee05e2
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that does not run a test case if the device does not have a given service.
+ */
+public class RequiredServiceRule implements TestRule {
+    private static final String TAG = "RequiredServiceRule";
+
+    private final String mService;
+    private final boolean mHasService;
+
+    /**
+     * Creates a rule for the given service.
+     */
+    public RequiredServiceRule(@NonNull String service) {
+        mService = service;
+        mHasService = hasService(service);
+    }
+
+    @Override
+    public Statement apply(@NonNull Statement base, @NonNull Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                if (!mHasService) {
+                    Log.d(TAG, "skipping "
+                            + description.getClassName() + "#" + description.getMethodName()
+                            + " because device does not have service '" + mService + "'");
+                    return;
+                }
+                base.evaluate();
+            }
+        };
+    }
+
+    private static boolean hasService(@NonNull String service) {
+        // TODO: ideally should call SystemServiceManager directly, but we would need to open
+        // some @Testing APIs for that.
+        String command = "service check " + service;
+        try {
+            String commandOutput = SystemUtil.runShellCommand(
+                    InstrumentationRegistry.getInstrumentation(), command);
+            return !commandOutput.contains("not found");
+        } catch (Exception e) {
+            Log.w(TAG, "Exception running '" + command + "': " + e);
+            return false;
+        }
+    }
+}
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 32fffdb..5cc78d2 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -574,7 +574,7 @@
     }
 
     private void checkDataConnection(String[] parts) {
-        assertEquals(25, parts.length);
+        assertEquals(26, parts.length);
         assertInteger(parts[4]);  // none
         assertInteger(parts[5]);  // gprs
         assertInteger(parts[6]);  // edge
@@ -595,7 +595,8 @@
         assertInteger(parts[21]); // td_scdma
         assertInteger(parts[22]); // iwlan
         assertInteger(parts[23]); // lte_ca
-        assertInteger(parts[24]); // other
+        assertInteger(parts[24]); // nr
+        assertInteger(parts[25]); // other
     }
 
     private void checkWifiState(String[] parts) {
diff --git a/tests/JobScheduler/AndroidTest.xml b/tests/JobScheduler/AndroidTest.xml
index 4820aa2..83365d7 100644
--- a/tests/JobScheduler/AndroidTest.xml
+++ b/tests/JobScheduler/AndroidTest.xml
@@ -21,6 +21,10 @@
         <option name="test-file-name" value="CtsJobSchedulerTestCases.apk" />
         <option name="test-file-name" value="CtsJobTestApp.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.jobscheduler.cts" />
         <option name="runtime-hint" value="2m" />
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConstraintTest.java
index 75f312a..f3a895e 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConstraintTest.java
@@ -105,7 +105,6 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        SystemUtil.runShellCommand(getInstrumentation(), "cmd thermalservice override-status 0");
         kTestEnvironment.setUp();
         kTriggerTestEnvironment.setUp();
         mJobScheduler.cancelAll();
@@ -118,7 +117,6 @@
             SystemUtil.runShellCommand(getInstrumentation(), "cmd devicestoragemonitor reset");
             mStorageStateChanged = false;
         }
-        SystemUtil.runShellCommand(getInstrumentation(), "cmd thermalservice reset");
     }
 
     /**
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 4bbf328..ffe684d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -119,8 +119,6 @@
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        // Lock thermal service to not throttling
-        mUiDevice.executeShellCommand("cmd thermalservice override-status 0");
         mPowerManager = mContext.getSystemService(PowerManager.class);
         mDeviceInDoze = mPowerManager.isDeviceIdleMode();
         mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
@@ -275,8 +273,6 @@
 
         Thread.sleep(500); // To avoid any race between unregister and the next register in setUp
         waitUntilTestAppNotInTempWhitelist();
-        // Reset thermal service
-        mUiDevice.executeShellCommand("cmd thermalservice reset");
     }
 
     private boolean isTestAppTempWhitelisted() throws Exception {
diff --git a/tests/JobSchedulerSharedUid/AndroidTest.xml b/tests/JobSchedulerSharedUid/AndroidTest.xml
index db88860..183551d 100644
--- a/tests/JobSchedulerSharedUid/AndroidTest.xml
+++ b/tests/JobSchedulerSharedUid/AndroidTest.xml
@@ -23,6 +23,10 @@
         <option name="test-file-name" value="CtsJobSchedulerSharedUid.apk" />
         <option name="test-file-name" value="CtsJobSharedUidTestApp.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.jobscheduler.cts.shareduidtests" />
         <option name="runtime-hint" value="2m" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index f263d24..c86cbdf 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -72,7 +72,6 @@
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.FlakyTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -193,7 +192,6 @@
         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
     }
 
-    @FlakyTest(bugId = 116260122)
     @MediumTest
     @Presubmit
     @Test
@@ -364,7 +362,6 @@
         assertNotNull("Did not receive expected event: " + expected, awaitedTextChangeEvent);
     }
 
-    @FlakyTest(bugId = 114543540)
     @MediumTest
     @Presubmit
     @Test
@@ -403,7 +400,6 @@
         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
     }
 
-    @FlakyTest(bugId = 114543540)
     @MediumTest
     @AppModeFull
     @SuppressWarnings("deprecation")
@@ -553,7 +549,6 @@
     }
 
     @AppModeFull
-    @FlakyTest(bugId = 116260122)
     @MediumTest
     @Presubmit
     @Test
@@ -633,7 +628,6 @@
         }
     }
 
-    @FlakyTest(bugId = 114543540)
     @MediumTest
     @Presubmit
     @Test
@@ -663,7 +657,6 @@
                 editTextNode.isHeading());
     }
 
-    @FlakyTest(bugId = 116260122)
     @MediumTest
     @Presubmit
     @Test
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index ecf34b9..06da001 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -33,7 +33,6 @@
 import android.app.UiAutomation;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.FlakyTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -90,7 +89,6 @@
                 sInstrumentation, sUiAutomation, mActivityRule);
     }
 
-    @FlakyTest(bugId = 114543540)
     @MediumTest
     @Presubmit
     @Test
@@ -190,7 +188,6 @@
         assertFalse(rootLinearLayout.isAccessibilityFocused());
     }
 
-    @FlakyTest(bugId = 116260122)
     @MediumTest
     @Presubmit
     @Test
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index af1b555..995a2c7 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -35,6 +35,8 @@
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.OisSample;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.MandatoryStreamCombination;
+import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.CamcorderProfile;
@@ -160,10 +162,10 @@
     }
 
     /**
-     * Test for making sure the required output combinations for each hardware level and capability
-     * work as expected.
+     * Test for making sure the required logical output combinations for each hardware level and
+     * capability work as expected.
      */
-    public void testMandatoryOutputCombinations() throws Exception {
+    public void testMandatoryLogicalOutputCombinations() throws Exception {
         /**
          * Tables for maximum sizes to try for each hardware level and capability.
          *
@@ -173,59 +175,6 @@
          * Each row of the table is a set of (format, max resolution) pairs, using the below consts
          */
 
-        // Enum values are defined in MaxStreamSizes
-        final int[][] LEGACY_COMBINATIONS = {
-            // Simple preview, GPU video processing, or no-preview video recording
-            {PRIV, MAXIMUM},
-            // No-viewfinder still image capture
-            {JPEG, MAXIMUM},
-            // In-application video/image processing
-            {YUV,  MAXIMUM},
-            // Standard still imaging.
-            {PRIV, PREVIEW,  JPEG, MAXIMUM},
-            // In-app processing plus still capture.
-            {YUV,  PREVIEW,  JPEG, MAXIMUM},
-            // Standard recording.
-            {PRIV, PREVIEW,  PRIV, PREVIEW},
-            // Preview plus in-app processing.
-            {PRIV, PREVIEW,  YUV,  PREVIEW},
-            // Still capture plus in-app processing.
-            {PRIV, PREVIEW,  YUV,  PREVIEW,  JPEG, MAXIMUM}
-        };
-
-        final int[][] LIMITED_COMBINATIONS = {
-            // High-resolution video recording with preview.
-            {PRIV, PREVIEW,  PRIV, RECORD },
-            // High-resolution in-app video processing with preview.
-            {PRIV, PREVIEW,  YUV , RECORD },
-            // Two-input in-app video processing.
-            {YUV , PREVIEW,  YUV , RECORD },
-            // High-resolution recording with video snapshot.
-            {PRIV, PREVIEW,  PRIV, RECORD,   JPEG, RECORD  },
-            // High-resolution in-app processing with video snapshot.
-            {PRIV, PREVIEW,  YUV,  RECORD,   JPEG, RECORD  },
-            // Two-input in-app processing with still capture.
-            {YUV , PREVIEW,  YUV,  PREVIEW,  JPEG, MAXIMUM }
-        };
-
-        final int[][] BURST_COMBINATIONS = {
-            // Maximum-resolution GPU processing with preview.
-            {PRIV, PREVIEW,  PRIV, MAXIMUM },
-            // Maximum-resolution in-app processing with preview.
-            {PRIV, PREVIEW,  YUV,  MAXIMUM },
-            // Maximum-resolution two-input in-app processsing.
-            {YUV,  PREVIEW,  YUV,  MAXIMUM },
-        };
-
-        final int[][] FULL_COMBINATIONS = {
-            // Video recording with maximum-size video snapshot.
-            {PRIV, PREVIEW,  PRIV, PREVIEW,  JPEG, MAXIMUM },
-            // Standard video recording plus maximum-resolution in-app processing.
-            {YUV,  VGA,      PRIV, PREVIEW,  YUV,  MAXIMUM },
-            // Preview plus two-input maximum-resolution in-app processing.
-            {YUV,  VGA,      YUV,  PREVIEW,  YUV,  MAXIMUM }
-        };
-
         final int[][] RAW_COMBINATIONS = {
             // No-preview DNG capture.
             {RAW,  MAXIMUM },
@@ -245,16 +194,7 @@
             {YUV,  PREVIEW,  JPEG, MAXIMUM,  RAW, MAXIMUM}
         };
 
-        final int[][] LEVEL_3_COMBINATIONS = {
-            // In-app viewfinder analysis with dynamic selection of output format
-            {PRIV, PREVIEW, PRIV, VGA, YUV, MAXIMUM, RAW, MAXIMUM},
-            // In-app viewfinder analysis with dynamic selection of output format
-            {PRIV, PREVIEW, PRIV, VGA, JPEG, MAXIMUM, RAW, MAXIMUM}
-        };
-
-        final int[][][] TABLES =
-                { LEGACY_COMBINATIONS, LIMITED_COMBINATIONS, BURST_COMBINATIONS, FULL_COMBINATIONS,
-                  RAW_COMBINATIONS, LEVEL_3_COMBINATIONS };
+        final int[][][] TABLES = { RAW_COMBINATIONS };
 
         sanityCheckConfigurationTables(TABLES);
 
@@ -271,58 +211,13 @@
                 Log.v(TAG, "StreamConfigurationMap: " + streamConfigurationMapString);
             }
 
-            // Always run legacy-level tests for color-supporting devices
-
-            if (mStaticInfo.isColorOutputSupported()) {
-                for (int[] config : LEGACY_COMBINATIONS) {
-                    testOutputCombination(id, config, maxSizes);
-                }
-            }
-
             // Then run higher-level tests if applicable
-
             if (!mStaticInfo.isHardwareLevelLegacy()) {
-
-                // If not legacy, at least limited, so run limited-level tests
-
-                if (mStaticInfo.isColorOutputSupported()) {
-                    for (int[] config : LIMITED_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                }
-
-                // Check for BURST_CAPTURE, FULL and RAW and run those if appropriate
-
-                if (mStaticInfo.isCapabilitySupported(
-                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
-                    for (int[] config : BURST_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                }
-
-                if (mStaticInfo.isHardwareLevelAtLeastFull()) {
-                    for (int[] config : FULL_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                }
-
-                if (mStaticInfo.isCapabilitySupported(
-                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                    for (int[] config : RAW_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                } else if (mStaticInfo.isLogicalMultiCamera()) {
+                if (mStaticInfo.isLogicalMultiCamera()) {
                     for (int[] config : RAW_COMBINATIONS) {
                         testMultiCameraOutputCombination(id, config, maxSizes);
                     }
                 }
-
-                if (mStaticInfo.isHardwareLevelAtLeast(
-                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3)) {
-                    for (int[] config: LEVEL_3_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                }
             }
 
             closeDevice(id);
@@ -330,105 +225,24 @@
     }
 
     /**
-     * Test for making sure the required reprocess input/output combinations for each hardware
-     * level and capability work as expected.
+     * Test for making sure the mandatory stream combinations work as expected.
      */
-    public void testMandatoryReprocessConfigurations() throws Exception {
-
-        /**
-         * For each stream combination, verify that
-         *    1. A reprocessable session can be created using the stream combination.
-         *    2. Reprocess capture requests targeting YUV and JPEG outputs are successful.
-         */
-        final int[][] LIMITED_COMBINATIONS = {
-            // Input           Outputs
-            {PRIV, MAXIMUM,    JPEG, MAXIMUM},
-            {YUV , MAXIMUM,    JPEG, MAXIMUM},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, JPEG, MAXIMUM},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, JPEG, MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, JPEG, MAXIMUM},
-            {YUV , MAXIMUM,    YUV , PREVIEW, JPEG, MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
-            {YUV,  MAXIMUM,    YUV , PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
-        };
-
-        final int[][] FULL_COMBINATIONS = {
-            // Input           Outputs
-            {YUV , MAXIMUM,    PRIV, PREVIEW},
-            {YUV , MAXIMUM,    YUV , PREVIEW},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, YUV , RECORD},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, YUV , RECORD},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, YUV , MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, YUV , MAXIMUM},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
-        };
-
-        final int[][] RAW_COMBINATIONS = {
-            // Input           Outputs
-            {PRIV, MAXIMUM,    YUV , PREVIEW, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    YUV , PREVIEW, RAW , MAXIMUM},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    YUV , PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    YUV , PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
-        };
-
-        final int[][] LEVEL_3_COMBINATIONS = {
-            // Input          Outputs
-            // In-app viewfinder analysis with YUV->YUV ZSL and RAW
-            {YUV , MAXIMUM,   PRIV, PREVIEW, PRIV, VGA, RAW, MAXIMUM},
-            // In-app viewfinder analysis with PRIV->JPEG ZSL and RAW
-            {PRIV, MAXIMUM,   PRIV, PREVIEW, PRIV, VGA, RAW, MAXIMUM, JPEG, MAXIMUM},
-            // In-app viewfinder analysis with YUV->JPEG ZSL and RAW
-            {YUV , MAXIMUM,   PRIV, PREVIEW, PRIV, VGA, RAW, MAXIMUM, JPEG, MAXIMUM},
-        };
-
-        final int[][][] TABLES =
-                { LIMITED_COMBINATIONS, FULL_COMBINATIONS, RAW_COMBINATIONS, LEVEL_3_COMBINATIONS };
-
-        sanityCheckConfigurationTables(TABLES);
-
+    public void testMandatoryOutputCombinations() throws Exception {
         for (String id : mCameraIds) {
-            CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(id);
-            StaticMetadata staticInfo = new StaticMetadata(cc);
-            MaxStreamSizes maxSizes = new MaxStreamSizes(staticInfo, id, getContext());
-
-            // Skip the test for legacy devices.
-            if (staticInfo.isHardwareLevelLegacy()) {
+            openDevice(id);
+            MandatoryStreamCombination[] combinations =
+                    mStaticInfo.getCharacteristics().get(
+                            CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS);
+            if (combinations == null) {
+                Log.i(TAG, "No mandatory stream combinations for camera: " + id + " skip test");
+                closeDevice(id);
                 continue;
             }
 
-            openDevice(id);
-
             try {
-                for (int[] config : LIMITED_COMBINATIONS) {
-                    testReprocessStreamCombination(id, config, maxSizes, staticInfo);
-                }
-
-                // Check FULL devices
-                if (staticInfo.isHardwareLevelAtLeastFull()) {
-                    for (int[] config : FULL_COMBINATIONS) {
-                        testReprocessStreamCombination(id, config, maxSizes, staticInfo);
-                    }
-                }
-
-                // Check devices with RAW capability.
-                if (staticInfo.isCapabilitySupported(
-                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                    for (int[] config : RAW_COMBINATIONS) {
-                        testReprocessStreamCombination(id, config, maxSizes, staticInfo);
-                    }
-                }
-
-                if (mStaticInfo.isHardwareLevelAtLeast(
-                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3)) {
-                    for (int[] config: LEVEL_3_COMBINATIONS) {
-                        testReprocessStreamCombination(id, config, maxSizes, staticInfo);
+                for (MandatoryStreamCombination combination : combinations) {
+                    if (!combination.isReprocessable()) {
+                        testMandatoryStreamCombination(id, combination);
                     }
                 }
             } finally {
@@ -437,6 +251,568 @@
         }
     }
 
+    private void setupConfigurationTargets(List<MandatoryStreamInformation> streamsInfo,
+            List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets,
+            List<ImageReader> yuvTargets, List<ImageReader> y8Targets,
+            List<ImageReader> rawTargets, List<OutputConfiguration> outputConfigs,
+            int numBuffers, boolean substituteY8, MandatoryStreamInformation overrideStreamInfo,
+            List<String> overridePhysicalCameraIds, List<Size> overridePhysicalCameraSizes) {
+
+        ImageDropperListener imageDropperListener = new ImageDropperListener();
+
+        for (MandatoryStreamInformation streamInfo : streamsInfo) {
+            if (streamInfo.isInput()) {
+                continue;
+            }
+            int format = streamInfo.getFormat();
+            if (substituteY8 && (format == ImageFormat.YUV_420_888)) {
+                format = ImageFormat.Y8;
+            }
+            Surface newSurface;
+            Size[] availableSizes = new Size[streamInfo.getAvailableSizes().size()];
+            availableSizes = streamInfo.getAvailableSizes().toArray(availableSizes);
+            Size targetSize = CameraTestUtils.getMaxSize(availableSizes);
+
+            int numConfigs = 1;
+            if ((overrideStreamInfo == streamInfo) && overridePhysicalCameraIds != null &&
+                    overridePhysicalCameraIds.size() > 1) {
+                numConfigs = overridePhysicalCameraIds.size();
+            }
+            for (int j = 0; j < numConfigs; j++) {
+                targetSize = (numConfigs == 1) ? targetSize : overridePhysicalCameraSizes.get(j);
+                switch (format) {
+                    case ImageFormat.PRIVATE: {
+                        SurfaceTexture target = new SurfaceTexture(/*random int*/1);
+                        target.setDefaultBufferSize(targetSize.getWidth(), targetSize.getHeight());
+                        OutputConfiguration config = new OutputConfiguration(new Surface(target));
+                        if (numConfigs > 1) {
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        privTargets.add(target);
+                        break;
+                    }
+                    case ImageFormat.JPEG: {
+                        ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
+                                targetSize.getHeight(), format, numBuffers);
+                        target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        jpegTargets.add(target);
+                        break;
+                    }
+                    case ImageFormat.YUV_420_888: {
+                        ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
+                                targetSize.getHeight(), format, numBuffers);
+                        target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        yuvTargets.add(target);
+                        break;
+                    }
+                    case ImageFormat.Y8: {
+                        ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
+                                targetSize.getHeight(), format, numBuffers);
+                        target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        y8Targets.add(target);
+                        break;
+                    }
+                    case ImageFormat.RAW_SENSOR: {
+                        // targetSize could be null in the logical camera case where only
+                        // physical camera supports RAW stream.
+                        if (targetSize != null) {
+                            ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
+                                    targetSize.getHeight(), format, numBuffers);
+                            target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                            OutputConfiguration config =
+                                    new OutputConfiguration(target.getSurface());
+                            if (numConfigs > 1) {
+                                config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                            }
+                            outputConfigs.add(config);
+                            rawTargets.add(target);
+                        }
+                        break;
+                    }
+                    default:
+                        fail("Unknown output format " + format);
+                }
+            }
+        }
+    }
+
+    private void testMandatoryStreamCombination(String cameraId,
+            MandatoryStreamCombination combination) throws Exception {
+        // Check whether substituting YUV_888 format with Y8 format
+        boolean substituteY8 = false;
+        if (mStaticInfo.isMonochromeWithY8()) {
+            List<MandatoryStreamInformation> streamsInfo = combination.getStreamsInformation();
+            for (MandatoryStreamInformation streamInfo : streamsInfo) {
+                if (streamInfo.getFormat() == ImageFormat.YUV_420_888) {
+                    substituteY8 = true;
+                    break;
+                }
+            }
+        }
+
+        // Test camera output combination
+        Log.i(TAG, "Testing mandatory stream combination: " + combination.getDescription() +
+                " on camera: " + cameraId);
+        testMandatoryStreamCombination(cameraId, combination, /*substituteY8*/false);
+
+        if (substituteY8) {
+            testMandatoryStreamCombination(cameraId, combination, substituteY8);
+        }
+
+        // Test substituting YUV_888/RAW with physical streams for logical camera
+        if (mStaticInfo.isLogicalMultiCamera()) {
+            Log.i(TAG, String.format("Testing logical Camera %s, combination: %s",
+                    cameraId, combination.getDescription()));
+
+            testMultiCameraOutputCombination(cameraId, combination, /*substituteY8*/false);
+
+            if (substituteY8) {
+                testMultiCameraOutputCombination(cameraId, combination, substituteY8);
+            }
+        }
+    }
+
+    private void testMultiCameraOutputCombination(String cameraId,
+            MandatoryStreamCombination combination, boolean substituteY8) throws Exception {
+
+        // Timeout is relaxed by 1 second for LEGACY devices to reduce false positive rate in CTS
+        final int TIMEOUT_FOR_RESULT_MS = (mStaticInfo.isHardwareLevelLegacy()) ? 2000 : 1000;
+        final int MIN_RESULT_COUNT = 3;
+        Set<String> physicalCameraIds = mStaticInfo.getCharacteristics().getPhysicalCameraIds();
+
+        List<MandatoryStreamInformation> streamsInfo = combination.getStreamsInformation();
+        for (MandatoryStreamInformation streamInfo : streamsInfo) {
+            int format = streamInfo.getFormat();
+            if (substituteY8 && (format == ImageFormat.YUV_420_888)) {
+                format = ImageFormat.Y8;
+            }
+            if (format != ImageFormat.YUV_420_888 && format != ImageFormat.Y8 &&
+                    format != ImageFormat.RAW_SENSOR) {
+                continue;
+            }
+
+            // Find physical cameras with matching size.
+            Size[] availableSizes = new Size[streamInfo.getAvailableSizes().size()];
+            availableSizes = streamInfo.getAvailableSizes().toArray(availableSizes);
+            Size targetSize = CameraTestUtils.getMaxSize(availableSizes);
+
+            List<String> physicalCamerasForSize = new ArrayList<String>();
+            List<Size> physicalCameraSizes = new ArrayList<Size>();
+            for (String physicalId : physicalCameraIds) {
+                Size[] sizes = mAllStaticInfo.get(physicalId).getAvailableSizesForFormatChecked(
+                        format, StaticMetadata.StreamDirection.Output);
+                if (targetSize != null) {
+                    if (Arrays.asList(sizes).contains(targetSize)) {
+                        physicalCameraSizes.add(targetSize);
+                        physicalCamerasForSize.add(physicalId);
+                    }
+                } else if (format == ImageFormat.RAW_SENSOR && sizes.length > 0) {
+                    physicalCamerasForSize.add(physicalId);
+                    physicalCameraSizes.add(CameraTestUtils.getMaxSize(sizes));
+                }
+                if (physicalCamerasForSize.size() == 2) {
+                    break;
+                }
+            }
+            if (physicalCamerasForSize.size() < 2) {
+                continue;
+            }
+
+            // Set up outputs
+            List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
+            List<SurfaceTexture> privTargets = new ArrayList<SurfaceTexture>();
+            List<ImageReader> jpegTargets = new ArrayList<ImageReader>();
+            List<ImageReader> yuvTargets = new ArrayList<ImageReader>();
+            List<ImageReader> y8Targets = new ArrayList<ImageReader>();
+            List<ImageReader> rawTargets = new ArrayList<ImageReader>();
+
+            setupConfigurationTargets(streamsInfo, privTargets, jpegTargets, yuvTargets,
+                    y8Targets, rawTargets, outputConfigs, MIN_RESULT_COUNT, substituteY8,
+                    streamInfo, physicalCamerasForSize, physicalCameraSizes);
+
+            boolean haveSession = false;
+            try {
+                CaptureRequest.Builder requestBuilder =
+                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+                for (OutputConfiguration c : outputConfigs) {
+                    requestBuilder.addTarget(c.getSurface());
+                }
+
+                CameraCaptureSession.CaptureCallback mockCaptureCallback =
+                        mock(CameraCaptureSession.CaptureCallback.class);
+
+                assertTrue(String.format("Session configuration query %s failed",
+                        combination.getDescription()),
+                        checkSessionConfiguration(mCamera, mHandler, outputConfigs,
+                        /*inputConfig*/ null, SessionConfiguration.SESSION_REGULAR,
+                        /*expectedResult*/ true));
+
+                createSessionByConfigs(outputConfigs);
+                haveSession = true;
+                CaptureRequest request = requestBuilder.build();
+                mCameraSession.setRepeatingRequest(request, mockCaptureCallback, mHandler);
+
+                verify(mockCaptureCallback,
+                        timeout(TIMEOUT_FOR_RESULT_MS * MIN_RESULT_COUNT).atLeast(MIN_RESULT_COUNT))
+                        .onCaptureCompleted(
+                            eq(mCameraSession),
+                            eq(request),
+                            isA(TotalCaptureResult.class));
+                verify(mockCaptureCallback, never()).
+                        onCaptureFailed(
+                            eq(mCameraSession),
+                            eq(request),
+                            isA(CaptureFailure.class));
+
+            } catch (Throwable e) {
+                mCollector.addMessage(String.format("Output combination: %s failed due to: %s",
+                        combination.getDescription(), e.getMessage()));
+            }
+            if (haveSession) {
+                try {
+                    Log.i(TAG, String.format("Done camera %s, combination: %s, closing session",
+                                    cameraId, combination.getDescription()));
+                    stopCapture(/*fast*/false);
+                } catch (Throwable e) {
+                    mCollector.addMessage(
+                        String.format("Closing down for output combination: %s failed due to: %s",
+                                combination.getDescription(), e.getMessage()));
+                }
+            }
+
+            for (SurfaceTexture target : privTargets) {
+                target.release();
+            }
+            for (ImageReader target : jpegTargets) {
+                target.close();
+            }
+            for (ImageReader target : yuvTargets) {
+                target.close();
+            }
+            for (ImageReader target : y8Targets) {
+                target.close();
+            }
+            for (ImageReader target : rawTargets) {
+                target.close();
+            }
+        }
+    }
+
+    private void testMandatoryStreamCombination(String cameraId,
+            MandatoryStreamCombination combination, boolean substituteY8) throws Exception {
+
+        // Timeout is relaxed by 1 second for LEGACY devices to reduce false positive rate in CTS
+        final int TIMEOUT_FOR_RESULT_MS = (mStaticInfo.isHardwareLevelLegacy()) ? 2000 : 1000;
+        final int MIN_RESULT_COUNT = 3;
+
+        // Set up outputs
+        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
+        List<SurfaceTexture> privTargets = new ArrayList<SurfaceTexture>();
+        List<ImageReader> jpegTargets = new ArrayList<ImageReader>();
+        List<ImageReader> yuvTargets = new ArrayList<ImageReader>();
+        List<ImageReader> y8Targets = new ArrayList<ImageReader>();
+        List<ImageReader> rawTargets = new ArrayList<ImageReader>();
+
+        setupConfigurationTargets(combination.getStreamsInformation(), privTargets, jpegTargets,
+                yuvTargets, y8Targets, rawTargets, outputConfigs, MIN_RESULT_COUNT, substituteY8,
+                null /*overrideStreamInfo*/, null /*overridePhysicalCameraIds*/,
+                null /* overridePhysicalCameraSizes) */);
+
+        boolean haveSession = false;
+        try {
+            CaptureRequest.Builder requestBuilder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+            for (OutputConfiguration c : outputConfigs) {
+                requestBuilder.addTarget(c.getSurface());
+            }
+
+            CameraCaptureSession.CaptureCallback mockCaptureCallback =
+                    mock(CameraCaptureSession.CaptureCallback.class);
+
+            assertTrue(String.format("Session configuration query fro combination: %s failed",
+                    combination.getDescription()), checkSessionConfiguration(mCamera,
+                    mHandler, outputConfigs, /*inputConfig*/ null,
+                    SessionConfiguration.SESSION_REGULAR, /*expectedResult*/ true));
+
+            createSessionByConfigs(outputConfigs);
+            haveSession = true;
+            CaptureRequest request = requestBuilder.build();
+            mCameraSession.setRepeatingRequest(request, mockCaptureCallback, mHandler);
+
+            verify(mockCaptureCallback,
+                    timeout(TIMEOUT_FOR_RESULT_MS * MIN_RESULT_COUNT).atLeast(MIN_RESULT_COUNT))
+                    .onCaptureCompleted(
+                        eq(mCameraSession),
+                        eq(request),
+                        isA(TotalCaptureResult.class));
+            verify(mockCaptureCallback, never()).
+                    onCaptureFailed(
+                        eq(mCameraSession),
+                        eq(request),
+                        isA(CaptureFailure.class));
+
+        } catch (Throwable e) {
+            mCollector.addMessage(String.format("Mandatory stream combination: %s failed due: %s",
+                    combination.getDescription(), e.getMessage()));
+        }
+        if (haveSession) {
+            try {
+                Log.i(TAG, String.format("Done with camera %s, combination: %s, closing session",
+                                cameraId, combination.getDescription()));
+                stopCapture(/*fast*/false);
+            } catch (Throwable e) {
+                mCollector.addMessage(
+                    String.format("Closing down for combination: %s failed due to: %s",
+                            combination.getDescription(), e.getMessage()));
+            }
+        }
+
+        for (SurfaceTexture target : privTargets) {
+            target.release();
+        }
+        for (ImageReader target : jpegTargets) {
+            target.close();
+        }
+        for (ImageReader target : yuvTargets) {
+            target.close();
+        }
+        for (ImageReader target : y8Targets) {
+            target.close();
+        }
+        for (ImageReader target : rawTargets) {
+            target.close();
+        }
+    }
+
+    /**
+     * Test for making sure the required reprocess input/output combinations for each hardware
+     * level and capability work as expected.
+     */
+    public void testMandatoryReprocessConfigurations() throws Exception {
+        for (String id : mCameraIds) {
+            openDevice(id);
+            MandatoryStreamCombination[] combinations =
+                    mStaticInfo.getCharacteristics().get(
+                            CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS);
+            if (combinations == null) {
+                Log.i(TAG, "No mandatory stream combinations for camera: " + id + " skip test");
+                closeDevice(id);
+                continue;
+            }
+
+            try {
+                for (MandatoryStreamCombination combination : combinations) {
+                    if (combination.isReprocessable()) {
+                        Log.i(TAG, "Testing mandatory reprocessable stream combination: " +
+                                combination.getDescription() + " on camera: " + id);
+                        testMandatoryReprocessableStreamCombination(id, combination);
+                    }
+                }
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    private void testMandatoryReprocessableStreamCombination(String cameraId,
+            MandatoryStreamCombination combination) {
+        // Test reprocess stream combination
+        testMandatoryReprocessableStreamCombination(cameraId, combination, /*substituteY8*/false);
+
+        // Test substituting YUV_888 format with Y8 format in reprocess stream combination.
+        if (mStaticInfo.isMonochromeWithY8()) {
+            List<MandatoryStreamInformation> streamsInfo = combination.getStreamsInformation();
+            boolean hasY8 = false;
+            for (MandatoryStreamInformation streamInfo : streamsInfo) {
+                if (streamInfo.getFormat() == ImageFormat.YUV_420_888) {
+                    hasY8 = true;
+                    break;
+                }
+            }
+            if (hasY8) {
+                testMandatoryReprocessableStreamCombination(cameraId, combination, hasY8);
+            }
+        }
+    }
+
+    private void testMandatoryReprocessableStreamCombination(String cameraId,
+            MandatoryStreamCombination combination, boolean substituteY8) {
+
+        final int TIMEOUT_FOR_RESULT_MS = 3000;
+        final int NUM_REPROCESS_CAPTURES_PER_CONFIG = 3;
+
+        List<SurfaceTexture> privTargets = new ArrayList<>();
+        List<ImageReader> jpegTargets = new ArrayList<>();
+        List<ImageReader> yuvTargets = new ArrayList<>();
+        List<ImageReader> y8Targets = new ArrayList<>();
+        List<ImageReader> rawTargets = new ArrayList<>();
+        ArrayList<Surface> outputSurfaces = new ArrayList<>();
+        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
+        ImageReader inputReader = null;
+        ImageWriter inputWriter = null;
+        SimpleImageReaderListener inputReaderListener = new SimpleImageReaderListener();
+        SimpleCaptureCallback inputCaptureListener = new SimpleCaptureCallback();
+        SimpleCaptureCallback reprocessOutputCaptureListener = new SimpleCaptureCallback();
+
+        List<MandatoryStreamInformation> streamInfo = combination.getStreamsInformation();
+        assertTrue("Reprocessable stream combinations should have at least 3 or more streams",
+                    (streamInfo != null) && (streamInfo.size() >= 3));
+
+        assertTrue("The first mandatory stream information in a reprocessable combination must " +
+                "always be input", streamInfo.get(0).isInput());
+
+        List<Size> inputSizes = streamInfo.get(0).getAvailableSizes();
+        int inputFormat = streamInfo.get(0).getFormat();
+        if (substituteY8 && (inputFormat == ImageFormat.YUV_420_888)) {
+            inputFormat = ImageFormat.Y8;
+        }
+
+        try {
+            // The second stream information entry is the ZSL stream, which is configured
+            // separately.
+            setupConfigurationTargets(streamInfo.subList(2, streamInfo.size()), privTargets,
+                    jpegTargets, yuvTargets, y8Targets, rawTargets, outputConfigs,
+                    NUM_REPROCESS_CAPTURES_PER_CONFIG, substituteY8,  null /*overrideStreamInfo*/,
+                    null /*overridePhysicalCameraIds*/, null /* overridePhysicalCameraSizes) */);
+
+            outputSurfaces.ensureCapacity(outputConfigs.size());
+            for (OutputConfiguration config : outputConfigs) {
+                outputSurfaces.add(config.getSurface());
+            }
+
+            InputConfiguration inputConfig = new InputConfiguration(inputSizes.get(0).getWidth(),
+                    inputSizes.get(0).getHeight(), inputFormat);
+
+            // For each config, YUV and JPEG outputs will be tested. (For YUV/Y8 reprocessing,
+            // the YUV/Y8 ImageReader for input is also used for output.)
+            final boolean inputIsYuv = inputConfig.getFormat() == ImageFormat.YUV_420_888;
+            final boolean inputIsY8 = inputConfig.getFormat() == ImageFormat.Y8;
+            final boolean useYuv = inputIsYuv || yuvTargets.size() > 0;
+            final boolean useY8 = inputIsY8 || y8Targets.size() > 0;
+            final int totalNumReprocessCaptures =  NUM_REPROCESS_CAPTURES_PER_CONFIG * (
+                    ((inputIsYuv || inputIsY8) ? 1 : 0) +
+                    jpegTargets.size() + (useYuv ? yuvTargets.size() : y8Targets.size()));
+
+            // It needs 1 input buffer for each reprocess capture + the number of buffers
+            // that will be used as outputs.
+            inputReader = ImageReader.newInstance(inputConfig.getWidth(), inputConfig.getHeight(),
+                    inputConfig.getFormat(),
+                    totalNumReprocessCaptures + NUM_REPROCESS_CAPTURES_PER_CONFIG);
+            inputReader.setOnImageAvailableListener(inputReaderListener, mHandler);
+            outputSurfaces.add(inputReader.getSurface());
+
+            assertTrue(String.format("Session configuration query %s failed",
+                    combination.getDescription()),
+                    checkSessionConfigurationWithSurfaces(mCamera, mHandler, outputSurfaces,
+                    inputConfig, SessionConfiguration.SESSION_REGULAR, /*expectedResult*/ true));
+
+            // Verify we can create a reprocessable session with the input and all outputs.
+            BlockingSessionCallback sessionListener = new BlockingSessionCallback();
+            CameraCaptureSession session = configureReprocessableCameraSession(mCamera,
+                    inputConfig, outputSurfaces, sessionListener, mHandler);
+            inputWriter = ImageWriter.newInstance(session.getInputSurface(),
+                    totalNumReprocessCaptures);
+
+            // Prepare a request for reprocess input
+            CaptureRequest.Builder builder = mCamera.createCaptureRequest(
+                    CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+            builder.addTarget(inputReader.getSurface());
+
+            for (int i = 0; i < totalNumReprocessCaptures; i++) {
+                session.capture(builder.build(), inputCaptureListener, mHandler);
+            }
+
+            List<CaptureRequest> reprocessRequests = new ArrayList<>();
+            List<Surface> reprocessOutputs = new ArrayList<>();
+            if (inputIsYuv || inputIsY8) {
+                reprocessOutputs.add(inputReader.getSurface());
+            }
+
+            for (ImageReader reader : jpegTargets) {
+                reprocessOutputs.add(reader.getSurface());
+            }
+
+            for (ImageReader reader : yuvTargets) {
+                reprocessOutputs.add(reader.getSurface());
+            }
+
+            for (ImageReader reader : y8Targets) {
+                reprocessOutputs.add(reader.getSurface());
+            }
+
+            for (int i = 0; i < NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
+                for (Surface output : reprocessOutputs) {
+                    TotalCaptureResult result = inputCaptureListener.getTotalCaptureResult(
+                            TIMEOUT_FOR_RESULT_MS);
+                    builder =  mCamera.createReprocessCaptureRequest(result);
+                    inputWriter.queueInputImage(
+                            inputReaderListener.getImage(TIMEOUT_FOR_RESULT_MS));
+                    builder.addTarget(output);
+                    reprocessRequests.add(builder.build());
+                }
+            }
+
+            session.captureBurst(reprocessRequests, reprocessOutputCaptureListener, mHandler);
+
+            for (int i = 0; i < reprocessOutputs.size() * NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
+                TotalCaptureResult result = reprocessOutputCaptureListener.getTotalCaptureResult(
+                        TIMEOUT_FOR_RESULT_MS);
+            }
+        } catch (Throwable e) {
+            mCollector.addMessage(String.format("Reprocess stream combination %s failed due to: %s",
+                    combination.getDescription(), e.getMessage()));
+        } finally {
+            inputReaderListener.drain();
+            reprocessOutputCaptureListener.drain();
+
+            for (SurfaceTexture target : privTargets) {
+                target.release();
+            }
+
+            for (ImageReader target : jpegTargets) {
+                target.close();
+            }
+
+            for (ImageReader target : yuvTargets) {
+                target.close();
+            }
+
+            for (ImageReader target : y8Targets) {
+                target.close();
+            }
+
+            for (ImageReader target : rawTargets) {
+                target.close();
+            }
+
+            if (inputReader != null) {
+                inputReader.close();
+            }
+
+            if (inputWriter != null) {
+                inputWriter.close();
+            }
+        }
+    }
+
     public void testBasicTriggerSequence() throws Exception {
 
         for (String id : mCameraIds) {
@@ -1546,11 +1922,7 @@
         static final int RECORD  = 1;
         static final int MAXIMUM = 2;
         static final int VGA = 3;
-        static final int VGA_FULL_FOV = 4;
-        static final int MAX_30FPS = 5;
-        static final int RESOLUTION_COUNT = 6;
-
-        static final long FRAME_DURATION_30FPS_NSEC = (long) 1e9 / 30;
+        static final int RESOLUTION_COUNT = 4;
 
         public MaxStreamSizes(StaticMetadata sm, String cameraId, Context context) {
             Size[] privSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.PRIVATE,
@@ -1605,96 +1977,6 @@
                 }
             }
 
-            if (sm.isColorOutputSupported() && !sm.isHardwareLevelLegacy()) {
-                // VGA resolution, but with aspect ratio matching full res FOV
-                float fullFovAspect = maxYuvSizes[MAXIMUM].getWidth() / (float) maxYuvSizes[MAXIMUM].getHeight();
-                Size vgaFullFovSize = new Size(640, (int) (640 / fullFovAspect));
-
-                maxPrivSizes[VGA_FULL_FOV] = vgaFullFovSize;
-                maxYuvSizes[VGA_FULL_FOV] = vgaFullFovSize;
-                maxJpegSizes[VGA_FULL_FOV] = vgaFullFovSize;
-                if (sm.isMonochromeWithY8()) {
-                    maxY8Sizes[VGA_FULL_FOV] = vgaFullFovSize;
-                }
-
-                // Max resolution that runs at 30fps
-
-                Size maxPriv30fpsSize = null;
-                Size maxYuv30fpsSize = null;
-                Size maxY830fpsSize = null;
-                Size maxJpeg30fpsSize = null;
-                Comparator<Size> comparator = new SizeComparator();
-                for (Map.Entry<Size, Long> e :
-                             sm.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE).
-                             entrySet()) {
-                    Size s = e.getKey();
-                    Long minDuration = e.getValue();
-                    Log.d(TAG, String.format("Priv Size: %s, duration %d limit %d", s, minDuration, FRAME_DURATION_30FPS_NSEC));
-                    if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
-                        if (maxPriv30fpsSize == null ||
-                                comparator.compare(maxPriv30fpsSize, s) < 0) {
-                            maxPriv30fpsSize = s;
-                        }
-                    }
-                }
-                assertTrue("No PRIVATE resolution available at 30fps!", maxPriv30fpsSize != null);
-
-                for (Map.Entry<Size, Long> e :
-                             sm.getAvailableMinFrameDurationsForFormatChecked(
-                                     ImageFormat.YUV_420_888).
-                             entrySet()) {
-                    Size s = e.getKey();
-                    Long minDuration = e.getValue();
-                    Log.d(TAG, String.format("YUV Size: %s, duration %d limit %d", s, minDuration, FRAME_DURATION_30FPS_NSEC));
-                    if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
-                        if (maxYuv30fpsSize == null ||
-                                comparator.compare(maxYuv30fpsSize, s) < 0) {
-                            maxYuv30fpsSize = s;
-                        }
-                    }
-                }
-                assertTrue("No YUV_420_888 resolution available at 30fps!", maxYuv30fpsSize != null);
-
-                if (sm.isMonochromeWithY8()) {
-                    for (Map.Entry<Size, Long> e :
-                                 sm.getAvailableMinFrameDurationsForFormatChecked(
-                                         ImageFormat.Y8).
-                                 entrySet()) {
-                        Size s = e.getKey();
-                        Long minDuration = e.getValue();
-                        Log.d(TAG, String.format("Y8 Size: %s, duration %d limit %d",
-                                s, minDuration, FRAME_DURATION_30FPS_NSEC));
-                        if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
-                            if (maxY830fpsSize == null ||
-                                    comparator.compare(maxY830fpsSize, s) < 0) {
-                                maxY830fpsSize = s;
-                            }
-                        }
-                    }
-                    assertTrue("No Y8 resolution available at 30fps!", maxY830fpsSize != null);
-                }
-
-                for (Map.Entry<Size, Long> e :
-                             sm.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.JPEG).
-                             entrySet()) {
-                    Size s = e.getKey();
-                    Long minDuration = e.getValue();
-                    Log.d(TAG, String.format("JPEG Size: %s, duration %d limit %d", s, minDuration, FRAME_DURATION_30FPS_NSEC));
-                    if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
-                        if (maxJpeg30fpsSize == null ||
-                                comparator.compare(maxJpeg30fpsSize, s) < 0) {
-                            maxJpeg30fpsSize = s;
-                        }
-                    }
-                }
-                assertTrue("No JPEG resolution available at 30fps!", maxJpeg30fpsSize != null);
-
-                maxPrivSizes[MAX_30FPS] = maxPriv30fpsSize;
-                maxYuvSizes[MAX_30FPS] = maxYuv30fpsSize;
-                maxY8Sizes[MAX_30FPS] = maxY830fpsSize;
-                maxJpegSizes[MAX_30FPS] = maxJpeg30fpsSize;
-            }
-
             Size[] privInputSizes = configs.getInputSizes(ImageFormat.PRIVATE);
             maxInputPrivSize = privInputSizes != null ?
                     CameraTestUtils.getMaxSize(privInputSizes) : null;
@@ -1782,12 +2064,6 @@
                 case VGA:
                     b.append("VGA]");
                     break;
-                case VGA_FULL_FOV:
-                    b.append("VGA_FULL_FOV]");
-                    break;
-                case MAX_30FPS:
-                    b.append("MAX_30FPS]");
-                    break;
                 default:
                     b.append("UNK]");
                     break;
@@ -1795,339 +2071,6 @@
         }
     }
 
-    /**
-     * Return an InputConfiguration for a given reprocess configuration.
-     */
-    private InputConfiguration getInputConfig(int[] reprocessConfig, MaxStreamSizes maxSizes) {
-        int format;
-        Size size;
-
-        if (reprocessConfig[1] != MAXIMUM) {
-            throw new IllegalArgumentException("Test only supports MAXIMUM input");
-        }
-
-        switch (reprocessConfig[0]) {
-            case PRIV:
-                format = ImageFormat.PRIVATE;
-                size = maxSizes.maxInputPrivSize;
-                break;
-            case YUV:
-                format = ImageFormat.YUV_420_888;
-                size = maxSizes.maxInputYuvSize;
-                break;
-            case Y8:
-                format = ImageFormat.Y8;
-                size = maxSizes.maxInputY8Size;
-                break;
-            default:
-                throw new IllegalArgumentException("Input format not supported: " +
-                        reprocessConfig[0]);
-        }
-
-        return new InputConfiguration(size.getWidth(), size.getHeight(), format);
-    }
-
-    private void testReprocessStreamCombination(String cameraId, int[] reprocessConfig,
-            MaxStreamSizes maxSizes, StaticMetadata staticInfo) throws Exception {
-        // Test reprocess stream combination
-        testSingleReprocessStreamCombination(cameraId, reprocessConfig, maxSizes, staticInfo);
-
-        // Test substituting YUV_888 format with Y8 format in reprocess stream combination.
-        if (mStaticInfo.isMonochromeWithY8()) {
-            int[] substitutedCfg = reprocessConfig.clone();
-            boolean hasY8 = false;
-            for (int i = 0; i < reprocessConfig.length; i += 2) {
-                if (substitutedCfg[i] == YUV) {
-                    substitutedCfg[i] = Y8;
-                    hasY8 = true;
-                }
-            }
-            if (hasY8) {
-                testSingleReprocessStreamCombination(cameraId, substitutedCfg, maxSizes, staticInfo);
-            }
-        }
-
-    }
-    private void testSingleReprocessStreamCombination(String cameraId, int[] reprocessConfig,
-            MaxStreamSizes maxSizes, StaticMetadata staticInfo) throws Exception {
-
-        Log.i(TAG, String.format("Testing Camera %s, reprocess config: %s", cameraId,
-                MaxStreamSizes.reprocessConfigToString(reprocessConfig)));
-
-        final int TIMEOUT_FOR_RESULT_MS = 3000;
-        final int NUM_REPROCESS_CAPTURES_PER_CONFIG = 3;
-
-        List<SurfaceTexture> privTargets = new ArrayList<>();
-        List<ImageReader> jpegTargets = new ArrayList<>();
-        List<ImageReader> yuvTargets = new ArrayList<>();
-        List<ImageReader> y8Targets = new ArrayList<>();
-        List<ImageReader> rawTargets = new ArrayList<>();
-        List<Surface> outputSurfaces = new ArrayList<>();
-        ImageReader inputReader = null;
-        ImageWriter inputWriter = null;
-        SimpleImageReaderListener inputReaderListener = new SimpleImageReaderListener();
-        SimpleCaptureCallback inputCaptureListener = new SimpleCaptureCallback();
-        SimpleCaptureCallback reprocessOutputCaptureListener = new SimpleCaptureCallback();
-
-        boolean supportYuvReprocess = staticInfo.isCapabilitySupported(
-                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
-        boolean supportOpaqueReprocess = staticInfo.isCapabilitySupported(
-                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
-
-        // Skip the configuration if the format is not supported for reprocessing.
-        if (((reprocessConfig[0] == YUV || reprocessConfig[0] == Y8) && !supportYuvReprocess) ||
-                (reprocessConfig[0] == PRIV && !supportOpaqueReprocess)) {
-            return;
-        }
-
-        try {
-            // reprocessConfig[2..] are additional outputs
-            setupConfigurationTargets(
-                    Arrays.copyOfRange(reprocessConfig, 2, reprocessConfig.length),
-                    maxSizes, privTargets, jpegTargets, yuvTargets, y8Targets,
-                    rawTargets, outputSurfaces, NUM_REPROCESS_CAPTURES_PER_CONFIG);
-
-            // reprocessConfig[0:1] is input
-            InputConfiguration inputConfig = getInputConfig(
-                    Arrays.copyOfRange(reprocessConfig, 0, 2), maxSizes);
-
-            // For each config, YUV and JPEG outputs will be tested. (For YUV/Y8 reprocessing,
-            // the YUV/Y8 ImageReader for input is also used for output.)
-            final boolean inputIsYuv = inputConfig.getFormat() == ImageFormat.YUV_420_888;
-            final boolean inputIsY8 = inputConfig.getFormat() == ImageFormat.Y8;
-            final boolean useYuv = inputIsYuv || yuvTargets.size() > 0;
-            final boolean useY8 = inputIsY8 || y8Targets.size() > 0;
-            final int totalNumReprocessCaptures =  NUM_REPROCESS_CAPTURES_PER_CONFIG * (
-                    ((inputIsYuv || inputIsY8) ? 1 : 0) +
-                    jpegTargets.size() + (useYuv ? yuvTargets.size() : y8Targets.size()));
-
-            // It needs 1 input buffer for each reprocess capture + the number of buffers
-            // that will be used as outputs.
-            inputReader = ImageReader.newInstance(inputConfig.getWidth(), inputConfig.getHeight(),
-                    inputConfig.getFormat(),
-                    totalNumReprocessCaptures + NUM_REPROCESS_CAPTURES_PER_CONFIG);
-            inputReader.setOnImageAvailableListener(inputReaderListener, mHandler);
-            outputSurfaces.add(inputReader.getSurface());
-
-            assertTrue(String.format("Session configuration query %s failed",
-                    MaxStreamSizes.reprocessConfigToString(reprocessConfig)),
-                    checkSessionConfigurationWithSurfaces(mCamera, mHandler, outputSurfaces,
-                    inputConfig, SessionConfiguration.SESSION_REGULAR, /*expectedResult*/ true));
-
-            // Verify we can create a reprocessable session with the input and all outputs.
-            BlockingSessionCallback sessionListener = new BlockingSessionCallback();
-            CameraCaptureSession session = configureReprocessableCameraSession(mCamera,
-                    inputConfig, outputSurfaces, sessionListener, mHandler);
-            inputWriter = ImageWriter.newInstance(session.getInputSurface(),
-                    totalNumReprocessCaptures);
-
-            // Prepare a request for reprocess input
-            CaptureRequest.Builder builder = mCamera.createCaptureRequest(
-                    CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
-            builder.addTarget(inputReader.getSurface());
-
-            for (int i = 0; i < totalNumReprocessCaptures; i++) {
-                session.capture(builder.build(), inputCaptureListener, mHandler);
-            }
-
-            List<CaptureRequest> reprocessRequests = new ArrayList<>();
-            List<Surface> reprocessOutputs = new ArrayList<>();
-            if (inputIsYuv || inputIsY8) {
-                reprocessOutputs.add(inputReader.getSurface());
-            }
-
-            for (ImageReader reader : jpegTargets) {
-                reprocessOutputs.add(reader.getSurface());
-            }
-
-            for (ImageReader reader : yuvTargets) {
-                reprocessOutputs.add(reader.getSurface());
-            }
-
-            for (ImageReader reader : y8Targets) {
-                reprocessOutputs.add(reader.getSurface());
-            }
-
-            for (int i = 0; i < NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
-                for (Surface output : reprocessOutputs) {
-                    TotalCaptureResult result = inputCaptureListener.getTotalCaptureResult(
-                            TIMEOUT_FOR_RESULT_MS);
-                    builder =  mCamera.createReprocessCaptureRequest(result);
-                    inputWriter.queueInputImage(
-                            inputReaderListener.getImage(TIMEOUT_FOR_RESULT_MS));
-                    builder.addTarget(output);
-                    reprocessRequests.add(builder.build());
-                }
-            }
-
-            session.captureBurst(reprocessRequests, reprocessOutputCaptureListener, mHandler);
-
-            for (int i = 0; i < reprocessOutputs.size() * NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
-                TotalCaptureResult result = reprocessOutputCaptureListener.getTotalCaptureResult(
-                        TIMEOUT_FOR_RESULT_MS);
-            }
-        } catch (Throwable e) {
-            mCollector.addMessage(String.format("Reprocess stream combination %s failed due to: %s",
-                    MaxStreamSizes.reprocessConfigToString(reprocessConfig), e.getMessage()));
-        } finally {
-            inputReaderListener.drain();
-            reprocessOutputCaptureListener.drain();
-
-            for (SurfaceTexture target : privTargets) {
-                target.release();
-            }
-
-            for (ImageReader target : jpegTargets) {
-                target.close();
-            }
-
-            for (ImageReader target : yuvTargets) {
-                target.close();
-            }
-
-            for (ImageReader target : y8Targets) {
-                target.close();
-            }
-
-            for (ImageReader target : rawTargets) {
-                target.close();
-            }
-
-            if (inputReader != null) {
-                inputReader.close();
-            }
-
-            if (inputWriter != null) {
-                inputWriter.close();
-            }
-        }
-    }
-
-    private void testOutputCombination(String cameraId, int[] config, MaxStreamSizes maxSizes)
-            throws Exception {
-
-
-        // Check whether substituting YUV_888 format with Y8 format
-        boolean substituteY8 = false;
-        int[] substitutedCfg = config.clone();
-        if (mStaticInfo.isMonochromeWithY8()) {
-            for (int i = 0; i < config.length; i += 2) {
-                if (substitutedCfg[i] == YUV) {
-                    substitutedCfg[i] = Y8;
-                    substituteY8 = true;
-                }
-            }
-        }
-
-        // Test camera output combination
-        Log.i(TAG, String.format("Testing single Camera %s, config %s",
-                cameraId, MaxStreamSizes.configToString(config)));
-        testSingleCameraOutputCombination(cameraId, config, maxSizes);
-
-        if (substituteY8) {
-            Log.i(TAG, String.format("Testing single Camera %s, config %s",
-                    cameraId, MaxStreamSizes.configToString(substitutedCfg)));
-            testSingleCameraOutputCombination(cameraId, substitutedCfg, maxSizes);
-        }
-
-        // Test substituting YUV_888/RAW with physical streams for logical camera
-        if (mStaticInfo.isLogicalMultiCamera()) {
-            Log.i(TAG, String.format("Testing logical Camera %s, config %s",
-                    cameraId, MaxStreamSizes.configToString(config)));
-
-            testMultiCameraOutputCombination(cameraId, config, maxSizes);
-
-            if (substituteY8) {
-                Log.i(TAG, String.format("Testing logical Camera %s, config %s",
-                        cameraId, MaxStreamSizes.configToString(substitutedCfg)));
-                testMultiCameraOutputCombination(cameraId, substitutedCfg, maxSizes);
-            }
-        }
-    }
-
-    private void testSingleCameraOutputCombination(String cameraId, int[] config,
-        MaxStreamSizes maxSizes) throws Exception {
-
-        // Timeout is relaxed by 1 second for LEGACY devices to reduce false positive rate in CTS
-        final int TIMEOUT_FOR_RESULT_MS = (mStaticInfo.isHardwareLevelLegacy()) ? 2000 : 1000;
-        final int MIN_RESULT_COUNT = 3;
-
-        // Set up outputs
-        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
-        List<SurfaceTexture> privTargets = new ArrayList<SurfaceTexture>();
-        List<ImageReader> jpegTargets = new ArrayList<ImageReader>();
-        List<ImageReader> yuvTargets = new ArrayList<ImageReader>();
-        List<ImageReader> rawTargets = new ArrayList<ImageReader>();
-        List<ImageReader> y8Targets = new ArrayList<ImageReader>();
-
-        setupConfigurationTargets(config, maxSizes, privTargets, jpegTargets, yuvTargets,
-                y8Targets, rawTargets, outputConfigs, MIN_RESULT_COUNT, -1 /*overrideStreamIndex*/,
-                null /*overridePhysicalCameraIds*/, null /*overridePhysicalCameraSizes*/);
-
-        boolean haveSession = false;
-        try {
-            CaptureRequest.Builder requestBuilder =
-                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
-
-            for (OutputConfiguration c : outputConfigs) {
-                requestBuilder.addTarget(c.getSurface());
-            }
-
-            CameraCaptureSession.CaptureCallback mockCaptureCallback =
-                    mock(CameraCaptureSession.CaptureCallback.class);
-
-            assertTrue(String.format("Session configuration query %s failed",
-                    MaxStreamSizes.configToString(config)), checkSessionConfiguration(mCamera,
-                    mHandler, outputConfigs, /*inputConfig*/ null,
-                    SessionConfiguration.SESSION_REGULAR, /*expectedResult*/ true));
-
-            createSessionByConfigs(outputConfigs);
-            haveSession = true;
-            CaptureRequest request = requestBuilder.build();
-            mCameraSession.setRepeatingRequest(request, mockCaptureCallback, mHandler);
-
-            verify(mockCaptureCallback,
-                    timeout(TIMEOUT_FOR_RESULT_MS * MIN_RESULT_COUNT).atLeast(MIN_RESULT_COUNT))
-                    .onCaptureCompleted(
-                        eq(mCameraSession),
-                        eq(request),
-                        isA(TotalCaptureResult.class));
-            verify(mockCaptureCallback, never()).
-                    onCaptureFailed(
-                        eq(mCameraSession),
-                        eq(request),
-                        isA(CaptureFailure.class));
-
-        } catch (Throwable e) {
-            mCollector.addMessage(String.format("Output combination %s failed due to: %s",
-                    MaxStreamSizes.configToString(config), e.getMessage()));
-        }
-        if (haveSession) {
-            try {
-                Log.i(TAG, String.format("Done with camera %s, config %s, closing session",
-                                cameraId, MaxStreamSizes.configToString(config)));
-                stopCapture(/*fast*/false);
-            } catch (Throwable e) {
-                mCollector.addMessage(
-                    String.format("Closing down for output combination %s failed due to: %s",
-                            MaxStreamSizes.configToString(config), e.getMessage()));
-            }
-        }
-
-        for (SurfaceTexture target : privTargets) {
-            target.release();
-        }
-        for (ImageReader target : jpegTargets) {
-            target.close();
-        }
-        for (ImageReader target : yuvTargets) {
-            target.close();
-        }
-        for (ImageReader target : rawTargets) {
-            target.close();
-        }
-    }
-
     private void testMultiCameraOutputCombination(String cameraId, int[] config,
         MaxStreamSizes maxSizes) throws Exception {
 
@@ -2253,21 +2196,6 @@
     private void setupConfigurationTargets(int[] configs, MaxStreamSizes maxSizes,
             List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets,
             List<ImageReader> yuvTargets, List<ImageReader> y8Targets,
-            List<ImageReader> rawTargets, List<Surface> outputSurfaces, int numBuffers) {
-        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration> ();
-
-        setupConfigurationTargets(configs, maxSizes, privTargets, jpegTargets, yuvTargets,
-                y8Targets, rawTargets, outputConfigs, numBuffers, -1 /*overrideStreamIndex*/,
-                null /*overridePhysicalCameraIds*/, null /* overridePhysicalCameraSizes) */);
-
-        for (OutputConfiguration outputConfig : outputConfigs) {
-            outputSurfaces.add(outputConfig.getSurface());
-        }
-    }
-
-    private void setupConfigurationTargets(int[] configs, MaxStreamSizes maxSizes,
-            List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets,
-            List<ImageReader> yuvTargets, List<ImageReader> y8Targets,
             List<ImageReader> rawTargets, List<OutputConfiguration> outputConfigs, int numBuffers,
             int overrideStreamIndex, List<String> overridePhysicalCameraIds,
             List<Size> overridePhysicalCameraSizes) {
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
index ee0ec57..a8866c5 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
@@ -19,10 +19,6 @@
 import static android.contentcaptureservice.cts.Helper.TAG;
 import static android.contentcaptureservice.cts.Helper.resetService;
 
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-
-import static org.junit.Assume.assumeFalse;
-
 import android.app.Application;
 import android.content.Context;
 import android.content.Intent;
@@ -34,13 +30,16 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.SafeCleanerRule;
+
 import org.junit.After;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+
 /**
  * Base class for all (or most :-) integration tests in this CTS suite.
  */
@@ -50,31 +49,41 @@
 
     protected static final Context sContext = InstrumentationRegistry.getTargetContext();
 
-    @Rule
-    public final RuleChain mLookAllTheseRules = RuleChain
-            .outerRule(getActivityTestRule());
-
     protected ActivitiesWatcher mActivitiesWatcher;
 
     private final Class<A> mActivityClass;
 
+    private final RequiredServiceRule mRequiredServiceRule =
+            new RequiredServiceRule("content_capture");
+    private final ContentCaptureLoggingTestRule mLoggingRule =
+            new ContentCaptureLoggingTestRule(TAG);
+
+    protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
+            .setDumper(mLoggingRule)
+            .add(() -> {
+                return CtsSmartSuggestionsService.getExceptions();
+            });
+
+    @Rule
+    public final RuleChain mLookAllTheseRules = RuleChain
+            //
+            // mRequiredServiceRule should be first so the test can be skipped right away
+            .outerRule(mRequiredServiceRule)
+            //
+            // mLoggingRule wraps the test but doesn't interfere with it
+            .around(mLoggingRule)
+            //
+            // mSafeCleanerRule will catch errors
+            .around(mSafeCleanerRule)
+            //
+            // Finally, let subclasses set their ActivityTestRule
+            .around(getActivityTestRule());
+
+
     protected AbstractContentCaptureIntegrationTest(@NonNull Class<A> activityClass) {
         mActivityClass = activityClass;
     }
 
-    @BeforeClass
-    public static void checkSupported() {
-        // TODO(b/119638958): use a @Rule to skip it and/or check for the Global Settings directly
-        final String checkService = runShellCommand("service check content_capture").trim();
-        final boolean notSupported = checkService.contains("not found");
-        if (notSupported) {
-            final String msg = "Skipping test because Content Capture is not supported on device";
-            Log.i(TAG, msg);
-            assumeFalse(msg, notSupported);
-            return;
-        }
-    }
-
     @Before
     public void registerLifecycleCallback() {
         Log.d(TAG, "Registering lifecycle callback");
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
index 9a7b071..7714b14 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
@@ -67,21 +67,16 @@
         watcher.waitFor(DESTROYED);
 
         final CtsSmartSuggestionsService service = CtsSmartSuggestionsService.getInstance();
-        try {
-            final Session session = service.getFinishedSession(BlankActivity.class);
+        final Session session = service.getFinishedSession(BlankActivity.class);
 
-            assertRightActivity(session, activity);
+        assertRightActivity(session, activity);
 
-            final List<ContentCaptureEvent> events = session.getEvents();
-            Log.v(TAG, "events: " + events);
-            assertThat(events).hasSize(4);
-            assertLifecycleEvent(events.get(0), TYPE_ACTIVITY_STARTED);
-            assertLifecycleEvent(events.get(1), TYPE_ACTIVITY_RESUMED);
-            assertLifecycleEvent(events.get(2), TYPE_ACTIVITY_PAUSED);
-            assertLifecycleEvent(events.get(3), TYPE_ACTIVITY_STOPPED);
-        } finally {
-            // TODO(b/119638958): move to @Rule SafeCleaner
-            CtsSmartSuggestionsService.assertNoExceptions();
-        }
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        assertThat(events).hasSize(4);
+        assertLifecycleEvent(events.get(0), TYPE_ACTIVITY_STARTED);
+        assertLifecycleEvent(events.get(1), TYPE_ACTIVITY_RESUMED);
+        assertLifecycleEvent(events.get(2), TYPE_ACTIVITY_PAUSED);
+        assertLifecycleEvent(events.get(3), TYPE_ACTIVITY_STOPPED);
     }
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureLoggingTestRule.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureLoggingTestRule.java
new file mode 100644
index 0000000..b0dca6f
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureLoggingTestRule.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Helper.TAG;
+import static android.contentcaptureservice.cts.common.ShellHelper.runShellCommand;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.SafeCleanerRule;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that improves ContentCapture-related logging by:
+ *
+ * <ol>
+ *   <li>Setting logging level to verbose before test start.
+ *   <li>Call {@code dumpsys} in case of failure.
+ * </ol>
+ */
+public class ContentCaptureLoggingTestRule implements TestRule, SafeCleanerRule.Dumper {
+
+    private final String mTag;
+    private boolean mDumped;
+
+    public ContentCaptureLoggingTestRule(String tag) {
+        mTag = tag;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                // TODO(b/119638958): set verbose logging once ContentCapture supports it
+                final String testName = description.getDisplayName();
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    dump(testName, t);
+                    throw t;
+                } finally {
+                    // TODO(b/119638958): recover logging level
+                }
+            }
+        };
+    }
+
+    @Override
+    public void dump(@NonNull String testName, @NonNull Throwable t) {
+        if (mDumped) {
+            Log.e(mTag, "dump(" + testName + "): already dumped");
+            return;
+        }
+        if ((t instanceof AssumptionViolatedException)) {
+            // This exception is used to indicate a test should be skipped and is
+            // ignored by JUnit runners - we don't need to dump it...
+            Log.w(TAG, "ignoring exception: " + t);
+            return;
+        }
+        // TODO(b/119638958, b/120784831): should dump to a file (and integrate with tradefed)
+        // instead of outputting to log directly...
+        Log.e(mTag, "Dumping after exception on " + testName, t);
+        final String autofillDump = runShellCommand("dumpsys content_capture");
+        Log.e(mTag, "content_capture dump: \n" + autofillDump);
+        final String activityDump = runShellCommand("dumpsys activity top --contentcapture");
+        Log.e(mTag, "top activity dump: \n" + activityDump);
+        mDumped = true;
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsSmartSuggestionsService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsSmartSuggestionsService.java
index 0983108..ca5b9cf 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsSmartSuggestionsService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsSmartSuggestionsService.java
@@ -171,15 +171,10 @@
     }
 
     /**
-     * Asserts that no exception was thrown while the service handlded requests.
+     * Gets the exceptions that were thrown while the service handlded requests.
      */
-    public static void assertNoExceptions() throws Exception {
-        if (sExceptions.isEmpty()) return;
-        if (sExceptions.size() == 1) {
-            throwException(sExceptions.get(0));
-        }
-        // TODO(b/119638958): use a MultipleExceptions class (from common)
-        throw new AssertionError("Multiple exceptions: " + sExceptions);
+    public static List<Throwable> getExceptions() throws Exception {
+        return Collections.unmodifiableList(sExceptions);
     }
 
     private void throwIllegalSessionStateException(@NonNull String fmt, @Nullable Object...args) {
@@ -210,16 +205,6 @@
         }
     }
 
-    private static void throwException(Throwable t) throws Exception {
-        if (t instanceof Exception) {
-            throw (Exception) t;
-        }
-        if (t instanceof Error) {
-            throw (Error) t;
-        }
-        throw new Exception(t);
-    }
-
     public final class Session {
         public final InteractionSessionId id;
         public final InteractionContext context;
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
index 1b886c4..a640e36 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
@@ -15,7 +15,7 @@
  */
 package android.contentcaptureservice.cts;
 
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static android.contentcaptureservice.cts.common.ShellHelper.runShellCommand;
 
 import android.os.SystemClock;
 import android.util.Log;
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
index b0c1267..72b92dc 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -71,52 +71,47 @@
         watcher.waitFor(DESTROYED);
 
         final CtsSmartSuggestionsService service = CtsSmartSuggestionsService.getInstance();
-        try {
-            final Session session = service.getFinishedSession(LoginActivity.class);
+        final Session session = service.getFinishedSession(LoginActivity.class);
 
-            assertRightActivity(session, activity);
+        assertRightActivity(session, activity);
 
-            final List<ContentCaptureEvent> events = session.getEvents();
-            Log.v(TAG, "events: " + events);
-            // TODO(b/119638958): ideally it should be 14 so it reflects just the views defined
-            // in the layout - right now it's generating events for 2 intermediate parents
-            // (android:action_mode_bar_stub and android:content), we should try to create an
-            // activity without them
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/119638958): ideally it should be 14 so it reflects just the views defined
+        // in the layout - right now it's generating events for 2 intermediate parents
+        // (android:action_mode_bar_stub and android:content), we should try to create an
+        // activity without them
 
-            final AutofillId rootId = activity.mRootView.getAutofillId();
+        final AutofillId rootId = activity.mRootView.getAutofillId();
 
-            assertThat(events).hasSize(18);
-            assertLifecycleEvent(events.get(0), TYPE_ACTIVITY_STARTED);
-            assertLifecycleEvent(events.get(1), TYPE_ACTIVITY_RESUMED);
-            assertViewAppeared(events.get(2), activity.mUsernameLabel, rootId);
-            assertViewAppeared(events.get(3), activity.mUsername, rootId);
-            assertViewAppeared(events.get(4), activity.mPasswordLabel, rootId);
-            assertViewAppeared(events.get(5), activity.mPassword, rootId);
-            // TODO(b/119638958): get rid of those intermediated parents
-            final View grandpa1 = (View) activity.mRootView.getParent();
-            final View grandpa2 = (View) grandpa1.getParent();
-            final View decorView = (View) grandpa2.getParent();
+        assertThat(events).hasSize(18);
+        assertLifecycleEvent(events.get(0), TYPE_ACTIVITY_STARTED);
+        assertLifecycleEvent(events.get(1), TYPE_ACTIVITY_RESUMED);
+        assertViewAppeared(events.get(2), activity.mUsernameLabel, rootId);
+        assertViewAppeared(events.get(3), activity.mUsername, rootId);
+        assertViewAppeared(events.get(4), activity.mPasswordLabel, rootId);
+        assertViewAppeared(events.get(5), activity.mPassword, rootId);
+        // TODO(b/119638958): get rid of those intermediated parents
+        final View grandpa1 = (View) activity.mRootView.getParent();
+        final View grandpa2 = (View) grandpa1.getParent();
+        final View decorView = (View) grandpa2.getParent();
 
-            assertViewAppeared(events.get(6), activity.mRootView, grandpa1.getAutofillId());
-            assertViewAppeared(events.get(7), grandpa1, grandpa2.getAutofillId());
-            assertViewAppeared(events.get(8), grandpa2, decorView.getAutofillId());
+        assertViewAppeared(events.get(6), activity.mRootView, grandpa1.getAutofillId());
+        assertViewAppeared(events.get(7), grandpa1, grandpa2.getAutofillId());
+        assertViewAppeared(events.get(8), grandpa2, decorView.getAutofillId());
 
-            // TODO(b/119638958): VIEW_DISAPPEARED events should be send before the activity
-            // stopped - if we don't deprecate the latter, we should change the manager to make sure
-            // they're send in that order (or dropped)
-            assertLifecycleEvent(events.get(9), TYPE_ACTIVITY_PAUSED);
-            assertLifecycleEvent(events.get(10), TYPE_ACTIVITY_STOPPED);
+        // TODO(b/119638958): VIEW_DISAPPEARED events should be send before the activity
+        // stopped - if we don't deprecate the latter, we should change the manager to make sure
+        // they're send in that order (or dropped)
+        assertLifecycleEvent(events.get(9), TYPE_ACTIVITY_PAUSED);
+        assertLifecycleEvent(events.get(10), TYPE_ACTIVITY_STOPPED);
 
-            assertViewDisappeared(events.get(11), grandpa2.getAutofillId());
-            assertViewDisappeared(events.get(12), grandpa1.getAutofillId());
-            assertViewDisappeared(events.get(13), activity.mRootView.getAutofillId());
-            assertViewDisappeared(events.get(14), activity.mUsernameLabel.getAutofillId());
-            assertViewDisappeared(events.get(15), activity.mUsername.getAutofillId());
-            assertViewDisappeared(events.get(16), activity.mPasswordLabel.getAutofillId());
-            assertViewDisappeared(events.get(17), activity.mPassword.getAutofillId());
-        } finally {
-            // TODO(b/119638958): move to @Rule SafeCleaner
-            CtsSmartSuggestionsService.assertNoExceptions();
-        }
+        assertViewDisappeared(events.get(11), grandpa2.getAutofillId());
+        assertViewDisappeared(events.get(12), grandpa1.getAutofillId());
+        assertViewDisappeared(events.get(13), activity.mRootView.getAutofillId());
+        assertViewDisappeared(events.get(14), activity.mUsernameLabel.getAutofillId());
+        assertViewDisappeared(events.get(15), activity.mUsername.getAutofillId());
+        assertViewDisappeared(events.get(16), activity.mPasswordLabel.getAutofillId());
+        assertViewDisappeared(events.get(17), activity.mPassword.getAutofillId());
     }
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ShellHelper.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ShellHelper.java
new file mode 100644
index 0000000..a9b1481
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ShellHelper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.contentcaptureservice.cts.common;
+
+import android.support.test.InstrumentationRegistry;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+/**
+ * Provides Shell-based utilities such as running a command.
+ */
+public final class ShellHelper {
+
+    private static final String TAG = "ShellHelper";
+
+    /**
+     * Runs a Shell command, returning a trimmed response.
+     */
+    @NonNull
+    public static String runShellCommand(@NonNull String template, Object... args) {
+        final String command = String.format(template, args);
+        Log.d(TAG, "runShellCommand(): " + command);
+        try {
+            final String result = SystemUtil
+                    .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+            return TextUtils.isEmpty(result) ? "" : result.trim();
+        } catch (Exception e) {
+            throw new RuntimeException("Command '" + command + "' failed: ", e);
+        }
+    }
+
+    private ShellHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/framework/base/activitymanager/AndroidManifest.xml b/tests/framework/base/activitymanager/AndroidManifest.xml
index 62abe73..d113cb4 100644
--- a/tests/framework/base/activitymanager/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -19,6 +19,7 @@
     package="android.server.cts.am">
     <uses-permission android:name="android.permission.READ_LOGS" />
     <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
 
     <application android:label="CtsActivityManagerDeviceTestCases">
         <uses-library android:name="android.test.runner" />
@@ -49,6 +50,23 @@
             android:label="MaxAspectRatioUnsetActivity"
             android:resizeableActivity="false" />
 
+        <activity
+            android:name="android.server.am.AspectRatioTests$MinAspectRatioActivity"
+            android:label="MinAspectRatioActivity"
+            android:minAspectRatio="5.0"
+            android:resizeableActivity="false" />
+
+        <activity
+            android:name="android.server.am.AspectRatioTests$MinAspectRatioResizeableActivity"
+            android:label="MinAspectRatioResizeableActivity"
+            android:minAspectRatio="5.0"
+            android:resizeableActivity="true" />
+
+        <activity
+            android:name="android.server.am.AspectRatioTests$MinAspectRatioUnsetActivity"
+            android:label="MinAspectRatioUnsetActivity"
+            android:resizeableActivity="false" />
+
         <activity android:name="android.server.am.ActivityManagerTestBase$SideActivity"
                   android:resizeableActivity="true"
                   android:taskAffinity="nobody.but.SideActivity"/>
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
index 808d06d..a313c35 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
@@ -16,24 +16,16 @@
 
 package android.server.am;
 
-import static android.content.pm.PackageManager.FEATURE_WATCH;
-
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeThat;
 
 import android.app.Activity;
-import android.content.Context;
 import android.platform.test.annotations.Presubmit;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
-import android.util.DisplayMetrics;
 import android.view.Display;
-import android.view.WindowManager;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -50,11 +42,8 @@
     // The max. aspect ratio the test activities are using.
     private static final float MAX_ASPECT_RATIO = 1.0f;
 
-    // The minimum supported device aspect ratio.
-    private static final float MIN_DEVICE_ASPECT_RATIO = 1.333f;
-
-    // The minimum supported device aspect ratio for watches.
-    private static final float MIN_WATCH_DEVICE_ASPECT_RATIO = 1.0f;
+    // The min. aspect ratio the test activities are using.
+    private static final float MIN_ASPECT_RATIO = 5.0f;
 
     // Test target activity that has maxAspectRatio="true" and resizeableActivity="false".
     public static class MaxAspectRatioActivity extends Activity {
@@ -74,6 +63,18 @@
     public static class MetaDataMaxAspectRatioActivity extends Activity {
     }
 
+    // Test target activity that has minAspectRatio="true" and resizeableActivity="false".
+    public static class MinAspectRatioActivity extends Activity {
+    }
+
+    // Test target activity that has minAspectRatio="5.0" and resizeableActivity="true".
+    public static class MinAspectRatioResizeableActivity extends Activity {
+    }
+
+    // Test target activity that has no minAspectRatio defined and resizeableActivity="false".
+    public static class MinAspectRatioUnsetActivity extends Activity {
+    }
+
     @Rule
     public ActivityTestRule<MaxAspectRatioActivity> mMaxAspectRatioActivity =
             new ActivityTestRule<>(MaxAspectRatioActivity.class,
@@ -94,22 +95,20 @@
             new ActivityTestRule<>(MaxAspectRatioUnsetActivity.class,
                     false /* initialTouchMode */, false /* launchActivity */);
 
-    @Test
-    public void testDeviceAspectRatio() {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final WindowManager wm = context.getSystemService(WindowManager.class);
-        final Display display = wm.getDefaultDisplay();
-        final DisplayMetrics metrics = new DisplayMetrics();
-        display.getRealMetrics(metrics);
+    @Rule
+    public ActivityTestRule<MinAspectRatioActivity> mMinAspectRatioActivity =
+            new ActivityTestRule<>(MinAspectRatioActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
 
-        float longSide = Math.max(metrics.widthPixels, metrics.heightPixels);
-        float shortSide = Math.min(metrics.widthPixels, metrics.heightPixels);
-        float deviceAspectRatio = longSide / shortSide;
-        float expectedMinAspectRatio = context.getPackageManager().hasSystemFeature(FEATURE_WATCH)
-                ? MIN_WATCH_DEVICE_ASPECT_RATIO : MIN_DEVICE_ASPECT_RATIO;
+    @Rule
+    public ActivityTestRule<MinAspectRatioResizeableActivity> mMinAspectRatioResizeableActivity =
+            new ActivityTestRule<>(MinAspectRatioResizeableActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
 
-        assertThat(deviceAspectRatio, greaterThanOrEqualTo(expectedMinAspectRatio));
-    }
+    @Rule
+    public ActivityTestRule<MinAspectRatioUnsetActivity> mMinAspectRatioUnsetActivity =
+            new ActivityTestRule<>(MinAspectRatioUnsetActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
 
     @Test
     public void testMaxAspectRatio() {
@@ -150,4 +149,35 @@
             assertThat(actual, greaterThanOrEqualToInexact(getDefaultDisplayAspectRatio()));
         });
     }
+
+    @Test
+    public void testMinAspectRatio() {
+        // Activity has a minAspectRatio, assert the ratio is at least that.
+        runAspectRatioTest(mMinAspectRatioActivity, (actual, displayId) -> {
+            assertThat(actual, greaterThanOrEqualToInexact(MIN_ASPECT_RATIO));
+        });
+    }
+
+    @Test
+    public void testMinAspectRatioResizeableActivity() {
+        // Since this activity is resizeable, the minAspectRatio should be ignored.
+        runAspectRatioTest(mMinAspectRatioResizeableActivity, (actual, displayId) -> {
+            // TODO(b/69982434): Add ability to get native aspect ratio non-default display.
+            assumeThat(displayId, is(Display.DEFAULT_DISPLAY));
+
+            assertThat(actual, lessThanOrEqualToInexact(getDefaultDisplayAspectRatio()));
+        });
+    }
+
+    @Test
+    public void testMinAspectRatioUnsetActivity() {
+        // Since this activity didn't set an explicit minAspectRatio, there should be no such
+        // ratio enforced.
+        runAspectRatioTest(mMinAspectRatioUnsetActivity, (actual, displayId) -> {
+            // TODO(b/69982434): Add ability to get native aspect ratio non-default display.
+            assumeThat(displayId, is(Display.DEFAULT_DISPLAY));
+
+            assertThat(actual, lessThanOrEqualToInexact(getDefaultDisplayAspectRatio()));
+        });
+    }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
index 739c05a..777823b 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
@@ -33,6 +33,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.KeyguardManager;
 import android.content.ComponentName;
 
 import org.junit.Before;
@@ -70,6 +71,23 @@
     }
 
     @Test
+    public void testDisableKeyguard_thenSettingCredential_reenablesKeyguard_b119322269() {
+        final KeyguardManager.KeyguardLock keyguardLock = mContext.getSystemService(
+                KeyguardManager.class).newKeyguardLock("KeyguardLockedTests");
+
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            keyguardLock.disableKeyguard();
+
+            lockScreenSession.setLockCredential();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+        } finally {
+            keyguardLock.reenableKeyguard();
+        }
+    }
+
+    @Test
     public void testDismissKeyguard() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.setLockCredential()
diff --git a/tests/framework/base/activitymanager/testsdk28/Android.mk b/tests/framework/base/activitymanager/testsdk28/Android.mk
new file mode 100644
index 0000000..e1bb155
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk28/Android.mk
@@ -0,0 +1,35 @@
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests optional
+
+LOCAL_PACKAGE_NAME := CtsActivityManagerDeviceSdk28TestCases
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../src/android/server/am/AspectRatioTestsBase.java
+
+LOCAL_SDK_VERSION := 28
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    cts-amwm-util
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/framework/base/activitymanager/testsdk28/AndroidManifest.xml b/tests/framework/base/activitymanager/testsdk28/AndroidManifest.xml
new file mode 100644
index 0000000..9ba1dc0
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk28/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.server.cts.am.testsdk28">
+
+    <uses-sdk android:targetSdkVersion="28" />
+
+    <application android:label="CtsActivityManagerDeviceSdk28TestCases">
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name="android.server.am.AspectRatioSdk28Tests$Sdk28MinAspectRatioActivity"
+            android:label="Sdk28MinAspectRatioActivity"
+            android:exported="true"
+            android:resizeableActivity="false" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.server.cts.am.testsdk28" />
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/testsdk28/AndroidTest.xml b/tests/framework/base/activitymanager/testsdk28/AndroidTest.xml
new file mode 100644
index 0000000..0040fce
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk28/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<configuration description="Config for CTS ActivityManager SDK 28 compatibility test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsActivityManagerDeviceSdk28TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.server.cts.am.testsdk28" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/framework/base/activitymanager/testsdk28/src/android/server/am/AspectRatioSdk28Tests.java b/tests/framework/base/activitymanager/testsdk28/src/android/server/am/AspectRatioSdk28Tests.java
new file mode 100644
index 0000000..7eaa464
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk28/src/android/server/am/AspectRatioSdk28Tests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+
+import static org.junit.Assert.assertThat;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Run: atest AspectRatioSdk28Tests
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class AspectRatioSdk28Tests extends AspectRatioTestsBase {
+
+    // The minimum supported device aspect ratio for pre-Q devices.
+    private static final float MIN_DEVICE_ASPECT_RATIO = 1.333f;
+
+    // The minimum supported device aspect ratio for watches.
+    private static final float MIN_WATCH_DEVICE_ASPECT_RATIO = 1.0f;
+
+
+    // Test target activity that has targetSdk="28" and resizeableActivity="false".
+    public static class Sdk28MinAspectRatioActivity extends Activity {
+    }
+
+    @Rule
+    public ActivityTestRule<?> mSdk28MinAspectRatioActivity = new ActivityTestRule<>(
+            Sdk28MinAspectRatioActivity.class, false /* initialTouchMode */,
+            false /* launchActivity */);
+
+    @Test
+    public void testMaxAspectRatioPreQActivity() {
+        boolean isWatch = InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(FEATURE_WATCH);
+        float minAspectRatio = isWatch ? MIN_WATCH_DEVICE_ASPECT_RATIO : MIN_DEVICE_ASPECT_RATIO;
+
+        runAspectRatioTest(mSdk28MinAspectRatioActivity, (actual, displayId) -> {
+            assertThat(actual, greaterThanOrEqualToInexact(minAspectRatio));
+        });
+    }
+}
diff --git a/tests/tests/batterysaving/AndroidTest.xml b/tests/tests/batterysaving/AndroidTest.xml
index aa0e786..fff0c0d 100644
--- a/tests/tests/batterysaving/AndroidTest.xml
+++ b/tests/tests/batterysaving/AndroidTest.xml
@@ -33,6 +33,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="am set-standby-bucket android.os.cts.batterysaving.app_target_api_25      10" />
         <option name="run-command" value="am set-standby-bucket android.os.cts.batterysaving.app_target_api_current 10" />
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 5570067..a975b5b 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -20,6 +20,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/cts/content" />
         <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
     </target_preparer>
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
diff --git a/tests/tests/media/res/raw/a_4_haptic.ogg b/tests/tests/media/res/raw/a_4_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/a_4_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/b_5_haptic.ogg b/tests/tests/media/res/raw/b_5_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/b_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/c_sharp_5_haptic.ogg b/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/e_5_haptic.ogg b/tests/tests/media/res/raw/e_5_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/e_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/g_sharp_5_haptic.ogg b/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index d59d689..c10bfbe 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -1463,6 +1463,11 @@
         }
     }
 
+    public void testIsHapticPlaybackSupported() throws Exception {
+        // Calling the API to make sure it doesn't crash.
+        Log.i(TAG, "isHapticPlaybackSupported: " + AudioManager.isHapticPlaybackSupported());
+    }
+
     private void setInterruptionFilter(int filter) throws Exception {
         mNm.setInterruptionFilter(filter);
         for (int i = 0; i < 5; i++) {
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolHapticTest.java b/tests/tests/media/src/android/media/cts/SoundPoolHapticTest.java
new file mode 100644
index 0000000..0dad9fa
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/SoundPoolHapticTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.media.cts.R;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class SoundPoolHapticTest extends SoundPoolTest {
+    // Test files are mocked audio-haptic coupled ogg files for regression testing.
+    // TODO: Update with actual audio-haptic ogg files.
+
+    @Override
+    protected int getSoundA() {
+        return R.raw.a_4_haptic;
+    }
+
+    @Override
+    protected int getSoundCs() {
+        return R.raw.c_sharp_5_haptic;
+    }
+
+    @Override
+    protected int getSoundE() {
+        return R.raw.e_5_haptic;
+    }
+
+    @Override
+    protected int getSoundB() {
+        return R.raw.b_5_haptic;
+    }
+
+    @Override
+    protected int getSoundGs() {
+        return R.raw.g_sharp_5_haptic;
+    }
+
+    @Override
+    protected String getFileName() {
+        return "a_4_haptic.ogg";
+    }
+}
diff --git a/tests/tests/simpleperf/Android.mk b/tests/tests/simpleperf/Android.mk
index 94c2a17..24152c6 100644
--- a/tests/tests/simpleperf/Android.mk
+++ b/tests/tests/simpleperf/Android.mk
@@ -18,6 +18,8 @@
 LOCAL_STATIC_LIBRARIES += \
   libbacktrace \
   libunwindstack \
+  libdexfile_support \
+  libdexfile_external \
   libdexfile \
   libziparchive \
   libz \
diff --git a/tests/tests/syncmanager/AndroidTest.xml b/tests/tests/syncmanager/AndroidTest.xml
index 66919ad..d8fd6a3 100644
--- a/tests/tests/syncmanager/AndroidTest.xml
+++ b/tests/tests/syncmanager/AndroidTest.xml
@@ -35,6 +35,8 @@
         <option name="run-command" value="am set-standby-bucket android.content.syncmanager.cts.app1 10" />
         <option name="run-command" value="am set-standby-bucket android.content.syncmanager.cts.app2 10" />
         <option name="run-command" value="setprop log.tag.SyncManager VERBOSE" />
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
         <option name="teardown-command" value="setprop log.tag.SyncManager INFO" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/telephony/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/SubscriptionManagerTest.java
index 7b0018a..f47b8d9 100644
--- a/tests/tests/telephony/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -314,6 +314,40 @@
         }
     }
 
+    @Test
+    public void testSettingOpportunisticSubscription() throws Exception {
+        if (!isSupported()) return;
+
+        // Set subscription to be opportunistic. This should fail
+        // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
+        try {
+            mSm.setOpportunistic(true, mSubId);
+            fail();
+        } catch (SecurityException expected) {
+        }
+
+        // Shouldn't crash.
+        SubscriptionInfo info = mSm.getActiveSubscriptionInfo(mSubId);
+        info.isOpportunistic();
+    }
+
+    @Test
+    public void testSettingSubscriptionMeteredNess() throws Exception {
+        if (!isSupported()) return;
+
+        // Set subscription to be un-metered. This should fail
+        // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
+        try {
+            mSm.setMetered(false, mSubId);
+            fail();
+        } catch (SecurityException expected) {
+        }
+
+        // Shouldn't crash.
+        SubscriptionInfo info = mSm.getActiveSubscriptionInfo(mSubId);
+        info.isMetered();
+    }
+
     private void assertOverrideSuccess(SubscriptionPlan... plans) {
         mSm.setSubscriptionPlans(mSubId, Arrays.asList(plans));
         mSm.setSubscriptionOverrideCongested(mSubId, false, 0);
diff --git a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
index a6ecd3b..5d17417 100644
--- a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
@@ -43,6 +43,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -55,6 +56,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.List;
+import java.util.Map;
 import java.util.regex.Pattern;
 
 /**
@@ -704,4 +706,41 @@
         } catch (SecurityException expected) {
         }
     }
+
+    /**
+     * Tests TelephonyManager.getCurrentEmergencyNumberList.
+     */
+    @Test
+    public void testGetCurrentEmergencyNumberList() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        Map<Integer, List<EmergencyNumber>> emergencyNumberList
+          = mTelephonyManager.getCurrentEmergencyNumberList();
+        // TODO enhance it later
+    }
+
+    /**
+     * Tests TelephonyManager.isCurrentEmergencyNumber.
+     */
+    @Test
+    public void testIsCurrentEmergencyNumber() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        boolean isEmergencyNumber = mTelephonyManager.isCurrentEmergencyNumber("911");
+        // TODO enhance it later
+    }
+
+    /**
+     * Tests TelephonyManager.isCurrentPotentialEmergencyNumber.
+     */
+    @Test
+    public void testIsCurrentPotentialEmergencyNumber() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        boolean isEmergencyNumber = mTelephonyManager.isCurrentPotentialEmergencyNumber("911");
+        // TODO enhance it later
+    }
 }
diff --git a/tests/tests/text/src/android/text/style/cts/LineBackgroundSpan_StandardTest.java b/tests/tests/text/src/android/text/style/cts/LineBackgroundSpan_StandardTest.java
index 9e5825d..3440484 100644
--- a/tests/tests/text/src/android/text/style/cts/LineBackgroundSpan_StandardTest.java
+++ b/tests/tests/text/src/android/text/style/cts/LineBackgroundSpan_StandardTest.java
@@ -50,8 +50,9 @@
         try {
             span.writeToParcel(p, 0);
             p.setDataPosition(0);
-            LineBackgroundSpan.Standard parcelSpan = new LineBackgroundSpan.Standard(p);
+            final LineBackgroundSpan.Standard parcelSpan = new LineBackgroundSpan.Standard(p);
             assertEquals(COLOR, parcelSpan.getColor());
+            assertEquals(span.getSpanTypeId(), parcelSpan.getSpanTypeId());
         } finally {
             p.recycle();
         }
@@ -72,7 +73,7 @@
 
         span.drawBackground(canvas, paint, left, right, top, baseline, bottom,
                 text, 0, text.length(), 0);
-        verify(canvas).drawRect(left, right, top, bottom, paint);
+        verify(canvas).drawRect(left, top, right, bottom, paint);
     }
 }
 
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/ConversationActionsTest.java b/tests/tests/view/src/android/view/textclassifier/cts/ConversationActionsTest.java
index ab56bce..54f6166 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/ConversationActionsTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/ConversationActionsTest.java
@@ -45,6 +45,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ConversationActionsTest {
+    private static final String ID = "ID";
+    private static final String CONVERSATION_ID = "conversation_id";
     private static final String TEXT = "TEXT";
     private static final Person PERSON = new Person.Builder().setKey(TEXT).build();
     private static final ZonedDateTime TIME =
@@ -170,6 +172,7 @@
                         .build();
         ConversationActions.Request request =
                 new ConversationActions.Request.Builder(Collections.singletonList(message))
+                        .setConversationId(CONVERSATION_ID)
                         .setHints(Collections.singletonList(ConversationActions.HINT_FOR_IN_APP))
                         .setMaxSuggestions(10)
                         .setTypeConfig(typeConfig)
@@ -217,20 +220,37 @@
     }
 
     @Test
-    public void testConversationActions() {
+    public void testConversationActions_full() {
         ConversationActions.ConversationAction conversationAction =
                 new ConversationActions.ConversationAction.Builder(
                         ConversationActions.TYPE_CALL_PHONE)
                         .build();
 
         ConversationActions conversationActions =
-                new ConversationActions(Arrays.asList(conversationAction));
+                new ConversationActions(Arrays.asList(conversationAction), ID);
 
         ConversationActions recovered =
                 parcelizeDeparcelize(conversationActions, ConversationActions.CREATOR);
 
-        assertConversationActions(conversationActions);
-        assertConversationActions(recovered);
+        assertFullConversationActions(conversationActions);
+        assertFullConversationActions(recovered);
+    }
+
+    @Test
+    public void testConversationActions_minimal() {
+        ConversationActions.ConversationAction conversationAction =
+                new ConversationActions.ConversationAction.Builder(
+                        ConversationActions.TYPE_CALL_PHONE)
+                        .build();
+
+        ConversationActions conversationActions =
+                new ConversationActions(Arrays.asList(conversationAction), null);
+
+        ConversationActions recovered =
+                parcelizeDeparcelize(conversationActions, ConversationActions.CREATOR);
+
+        assertMinimalConversationActions(conversationActions);
+        assertMinimalConversationActions(recovered);
     }
 
     private void assertFullMessage(ConversationActions.Message message) {
@@ -289,6 +309,7 @@
         assertThat(request.getHints()).isEmpty();
         assertThat(request.getMaxSuggestions()).isEqualTo(0);
         assertThat(request.getTypeConfig()).isNotNull();
+        assertThat(request.getConversationId()).isNull();
     }
 
     private void assertFullRequest(ConversationActions.Request request) {
@@ -298,6 +319,7 @@
         assertThat(request.getHints()).containsExactly(ConversationActions.HINT_FOR_IN_APP);
         assertThat(request.getMaxSuggestions()).isEqualTo(10);
         assertThat(request.getTypeConfig().shouldIncludeTypesFromTextClassifier()).isFalse();
+        assertThat(request.getConversationId()).isEqualTo(CONVERSATION_ID);
     }
 
     private void assertMinimalConversationAction(
@@ -316,10 +338,18 @@
         assertThat(conversationAction.getExtras().keySet()).containsExactly(TEXT);
     }
 
-    private void assertConversationActions(ConversationActions conversationActions) {
+    private void assertMinimalConversationActions(ConversationActions conversationActions) {
         assertThat(conversationActions.getConversationActions()).hasSize(1);
         assertThat(conversationActions.getConversationActions().get(0).getType())
                 .isEqualTo(ConversationActions.TYPE_CALL_PHONE);
+        assertThat(conversationActions.getId()).isNull();
+    }
+
+    private void assertFullConversationActions(ConversationActions conversationActions) {
+        assertThat(conversationActions.getConversationActions()).hasSize(1);
+        assertThat(conversationActions.getConversationActions().get(0).getType())
+                .isEqualTo(ConversationActions.TYPE_CALL_PHONE);
+        assertThat(conversationActions.getId()).isEqualTo(ID);
     }
 
     private <T extends Parcelable> T parcelizeDeparcelize(
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
index add14d9..1cd65b8 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -36,6 +36,7 @@
 import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextClassifierEvent;
 import android.view.textclassifier.TextLanguage;
 import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextSelection;
@@ -199,6 +200,12 @@
     }
 
     @Test
+    public void testOnTextClassifierEvent() {
+        // Doesn't crash.
+        mClassifier.onTextClassifierEvent(new TextClassifierEvent.Builder(0, 0).build());
+    }
+
+    @Test
     public void testLanguageDetection() {
         assertValidResult(mClassifier.detectLanguage(TEXT_LANGUAGE_REQUEST));
     }
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierEventTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierEventTest.java
new file mode 100644
index 0000000..f1ca661
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierEventTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextClassifierEvent;
+import android.widget.TextView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassifierEventTest {
+
+    @Test
+    public void testMinimumEvent() {
+        final TextClassifierEvent event = new TextClassifierEvent.Builder(
+                TextClassifierEvent.CATEGORY_UNDEFINED, TextClassifierEvent.TYPE_UNDEFINED)
+                .build();
+
+        assertThat(event.getEventCategory()).isEqualTo(TextClassifierEvent.CATEGORY_UNDEFINED);
+        assertThat(event.getEventType()).isEqualTo(TextClassifierEvent.TYPE_UNDEFINED);
+        assertThat(event.getEventIndex()).isEqualTo(0);
+        assertThat(event.getEventTime()).isEqualTo(0);
+        assertThat(event.getEntityType()).isNull();
+        assertThat(event.getRelativeWordStartIndex()).isEqualTo(0);
+        assertThat(event.getRelativeWordEndIndex()).isEqualTo(0);
+        assertThat(event.getRelativeSuggestedWordStartIndex()).isEqualTo(0);
+        assertThat(event.getRelativeSuggestedWordEndIndex()).isEqualTo(0);
+        assertThat(event.getLanguage()).isNull();
+        assertThat(event.getResultId()).isNull();
+        assertThat(event.getActionIndices()).isEmpty();
+        assertThat(event.getExtras()).isEqualTo(Bundle.EMPTY);
+        assertThat(event.getEventContext()).isNull();
+    }
+
+    @Test
+    public void testFullEvent() {
+        final Bundle extra = new Bundle();
+        extra.putString("key", "value");
+        final long now = System.currentTimeMillis();
+        final String resultId = "androidtc-en-v606-1234";
+        final TextClassifierEvent event = new TextClassifierEvent.Builder(
+                TextClassifierEvent.CATEGORY_LINKIFY,
+                TextClassifierEvent.TYPE_LINK_CLICKED)
+                .setEventIndex(2)
+                .setEventTime(now)
+                .setEntityType(TextClassifier.TYPE_ADDRESS)
+                .setRelativeWordStartIndex(1)
+                .setRelativeWordEndIndex(2)
+                .setRelativeSuggestedWordStartIndex(-1)
+                .setRelativeSuggestedWordEndIndex(3)
+                .setLanguage("en")
+                .setResultId(resultId)
+                .setActionIndices(1, 2, 5)
+                .setExtras(extra)
+                .setEventContext(new TextClassificationContext.Builder(
+                        "pkg", TextClassifier.WIDGET_TYPE_TEXTVIEW)
+                        .setWidgetVersion(TextView.class.getName())
+                        .build())
+                .build();
+
+        assertThat(event.getEventCategory()).isEqualTo(TextClassifierEvent.CATEGORY_LINKIFY);
+        assertThat(event.getEventType()).isEqualTo(TextClassifierEvent.TYPE_LINK_CLICKED);
+        assertThat(event.getEventIndex()).isEqualTo(2);
+        assertThat(event.getEventTime()).isEqualTo(now);
+        assertThat(event.getEntityType()).isEqualTo(TextClassifier.TYPE_ADDRESS);
+        assertThat(event.getRelativeWordStartIndex()).isEqualTo(1);
+        assertThat(event.getRelativeWordEndIndex()).isEqualTo(2);
+        assertThat(event.getRelativeSuggestedWordStartIndex()).isEqualTo(-1);
+        assertThat(event.getRelativeSuggestedWordEndIndex()).isEqualTo(3);
+        assertThat(event.getLanguage()).isEqualTo("en");
+        assertThat(event.getResultId()).isEqualTo(resultId);
+        assertThat(event.getActionIndices()).asList().containsExactly(1, 2, 5);
+        assertThat(event.getExtras().get("key")).isEqualTo("value");
+        assertThat(event.getEventContext().getPackageName()).isEqualTo("pkg");
+        assertThat(event.getEventContext().getWidgetType())
+                .isEqualTo(TextClassifier.WIDGET_TYPE_TEXTVIEW);
+        assertThat(event.getEventContext().getWidgetVersion()).isEqualTo(TextView.class.getName());
+    }
+
+    @Test
+    public void testParcelUnparcel() {
+        final Bundle extra = new Bundle();
+        extra.putString("k", "v");
+        final TextClassifierEvent event = new TextClassifierEvent.Builder(
+                TextClassifierEvent.CATEGORY_SELECTION,
+                TextClassifierEvent.TYPE_SELECTION_MODIFIED)
+                .setEventIndex(1)
+                .setEventTime(1000)
+                .setEntityType(TextClassifier.TYPE_DATE)
+                .setRelativeWordStartIndex(4)
+                .setRelativeWordEndIndex(3)
+                .setRelativeSuggestedWordStartIndex(2)
+                .setRelativeSuggestedWordEndIndex(1)
+                .setLanguage("de")
+                .setResultId("id")
+                .setActionIndices(3)
+                .setExtras(extra)
+                .setEventContext(new TextClassificationContext.Builder(
+                        InstrumentationRegistry.getTargetContext().getPackageName(),
+                        TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW)
+                        .setWidgetVersion("notification")
+                        .build())
+                .build();
+
+        final Parcel parcel = Parcel.obtain();
+        event.writeToParcel(parcel, event.describeContents());
+        parcel.setDataPosition(0);
+        final TextClassifierEvent result = TextClassifierEvent.CREATOR.createFromParcel(parcel);
+
+        assertThat(result.getEventCategory()).isEqualTo(TextClassifierEvent.CATEGORY_SELECTION);
+        assertThat(result.getEventType()).isEqualTo(TextClassifierEvent.TYPE_SELECTION_MODIFIED);
+        assertThat(result.getEventIndex()).isEqualTo(1);
+        assertThat(result.getEventTime()).isEqualTo(1000);
+        assertThat(result.getEntityType()).isEqualTo(TextClassifier.TYPE_DATE);
+        assertThat(result.getRelativeWordStartIndex()).isEqualTo(4);
+        assertThat(result.getRelativeWordEndIndex()).isEqualTo(3);
+        assertThat(result.getRelativeSuggestedWordStartIndex()).isEqualTo(2);
+        assertThat(result.getRelativeSuggestedWordEndIndex()).isEqualTo(1);
+        assertThat(result.getLanguage()).isEqualTo("de");
+        assertThat(result.getResultId()).isEqualTo("id");
+        assertThat(result.getActionIndices()).asList().containsExactly(3);
+        assertThat(result.getExtras().get("k")).isEqualTo("v");
+        assertThat(result.getEventContext().getPackageName())
+                .isEqualTo(InstrumentationRegistry.getTargetContext().getPackageName());
+        assertThat(result.getEventContext().getWidgetType())
+                .isEqualTo(TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW);
+        assertThat(result.getEventContext().getWidgetVersion()).isEqualTo("notification");
+    }
+}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
index 9212f0f..03e080e 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
@@ -168,7 +168,7 @@
         final TextSelection.Request request = new TextSelection.Request.Builder(TEXT, START, END)
                 .setDefaultLocales(LOCALES)
                 .build();
-        assertEquals(TEXT, request.getText());
+        assertEquals(TEXT, request.getText().toString());
         assertEquals(START, request.getStartIndex());
         assertEquals(END, request.getEndIndex());
         assertEquals(LOCALES, request.getDefaultLocales());
diff --git a/tests/tests/widget/res/layout/textview_isHorizontallyScrolling_layout.xml b/tests/tests/widget/res/layout/textview_isHorizontallyScrolling_layout.xml
new file mode 100644
index 0000000..e2d3aaa
--- /dev/null
+++ b/tests/tests/widget/res/layout/textview_isHorizontallyScrolling_layout.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_default"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    <!-- In this case, scrollHorizontally is overwritten by singleLine, which is a bug. -->
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="true" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_default_singleLine_true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_true_singleLine_true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="true"
+            android:singleLine="true" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_false_singleLine_true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false"
+            android:singleLine="true" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_default_singleLine_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="false" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_false_singleLine_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false"
+            android:singleLine="false" />
+
+    <!-- In this case, scrollHorizontally is overwritten by singleLine, which is a bug. -->
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_true_singleLine_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="true"
+            android:singleLine="false" />
+</LinearLayout>
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewIsHorizontallyScrollingTest.java b/tests/tests/widget/src/android/widget/cts/TextViewIsHorizontallyScrollingTest.java
new file mode 100644
index 0000000..f3e3847
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextViewIsHorizontallyScrollingTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Test {@link TextView#isHorizontallyScrolling}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextViewIsHorizontallyScrollingTest {
+    private ViewGroup mViewGroup;
+
+    @Before
+    public void setup() {
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        mViewGroup = (ViewGroup) inflater.inflate(R.layout.textview_isHorizontallyScrolling_layout,
+                null);
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingDefaultIsFalse() {
+        final TextView textView = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default);
+
+        assertFalse(textView.isHorizontallyScrolling());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSameAsGiven() {
+        final TextView textView = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default);
+
+        textView.setHorizontallyScrolling(true);
+        assertTrue(textView.isHorizontallyScrolling());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingTrueToFalse() {
+        final TextView textView = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default);
+        textView.setHorizontallyScrolling(true);
+        assertTrue(textView.isHorizontallyScrolling());
+
+        textView.setHorizontallyScrolling(false);
+        assertFalse(textView.isHorizontallyScrolling());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSetInXML() {
+        final TextView textViewTrue = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_true);
+        // It should return true here. But because of this bug b/120448952,
+        // singleLine will overwrite scrollHorizontally.
+        assertFalse(textViewTrue.isHorizontallyScrolling());
+
+        final TextView textViewFalse = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_false);
+        assertFalse(textViewFalse.isHorizontallyScrolling());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSetInXML_returnTrueWhenSingleLineIsTrue() {
+        final TextView textViewDefault = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default_singleLine_true);
+        assertTrue(textViewDefault.isHorizontallyScrolling());
+
+        final TextView textViewTrue = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_true_singleLine_true);
+        assertTrue(textViewTrue.isHorizontallyScrolling());
+
+        final TextView textViewFalse = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_false_singleLine_true);
+        assertTrue(textViewFalse.isHorizontallyScrolling());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSetInXML_returnGivenValueWhenSingleLineIsFalse() {
+        final TextView textViewDefault = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default_singleLine_false);
+        assertFalse(textViewDefault.isHorizontallyScrolling());
+
+        final TextView textViewTrue = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_true_singleLine_false);
+        // It should return true here. But because of this bug b/120448952,
+        // singleLine will overwrite scrollHorizontally.
+        assertFalse(textViewTrue.isHorizontallyScrolling());
+
+        final TextView textViewFalse = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_false_singleLine_false);
+        assertFalse(textViewFalse.isHorizontallyScrolling());
+    }
+}
diff --git a/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java b/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java
index e9c7ae0..ac8eed99 100644
--- a/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java
+++ b/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java
@@ -30,6 +30,7 @@
 import android.media.tv.TvContract.Programs;
 import android.net.Uri;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.compatibility.common.util.CtsAndroidTestCase;
 import com.android.compatibility.common.util.DeviceReportLog;
@@ -51,6 +52,7 @@
     private static final int TRANSACTION_RUNS = 100;
     private static final int QUERY_RUNS = 10;
     private static final String REPORT_LOG_NAME = "CtsTvProviderTestCases";
+    private static final String TAG = "TvProviderPerfTest";
 
     private ContentResolver mContentResolver;
     private String mInputId;
@@ -81,11 +83,16 @@
         if (!mHasTvInputFramework) return;
         double[] averages = new double[5];
 
-        // Insert
+        Log.d(TAG, "Insert");
         final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
         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) {
@@ -111,7 +118,7 @@
                 ResultUnit.MS);
         averages[0] = Stat.getAverage(applyBatchTimes);
 
-        // Update
+        Log.d(TAG, "Update");
         final String[] projection = { Channels._ID };
         try (final Cursor cursor = mContentResolver.query(Channels.CONTENT_URI,
                 projection, null, null, null)) {
@@ -139,7 +146,7 @@
                 ResultUnit.MS);
         averages[1] = Stat.getAverage(applyBatchTimes);
 
-        // Query channels
+        Log.d(TAG, "Query channels");
         applyBatchTimes = MeasureTime.measure(QUERY_RUNS, new MeasureRun() {
             @Override
             public void run(int i) {
@@ -155,7 +162,7 @@
                 ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[2] = Stat.getAverage(applyBatchTimes);
 
-        // Query a channel
+        Log.d(TAG, "Query a channel");
         try (final Cursor cursor = mContentResolver.query(Channels.CONTENT_URI,
                 projection, null, null, null)) {
             applyBatchTimes = MeasureTime.measure(QUERY_RUNS, new MeasureRun() {
@@ -175,7 +182,7 @@
                 ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[3] = Stat.getAverage(applyBatchTimes);
 
-        // Delete
+        Log.d(TAG, "Delete");
         applyBatchTimes = MeasureTime.measure(1, new MeasureRun() {
             @Override
             public void run(int i) {
@@ -195,7 +202,7 @@
         if (!mHasTvInputFramework) return;
         double[] averages = new double[7];
 
-        // Prepare (insert channels)
+        Log.d(TAG, "Prepare (insert channels)");
         final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
         final int TRANSACTION_SIZE = 1000;
         final int NUM_CHANNELS = 100;
@@ -222,7 +229,7 @@
             throw new RuntimeException(e);
         }
 
-        // Insert
+        Log.d(TAG, "Insert programs");
         double[] applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
             @Override
             public void run(int i) {
@@ -249,7 +256,7 @@
                 ResultUnit.MS);
         averages[0] = Stat.getAverage(applyBatchTimes);
 
-        // Update
+        Log.d(TAG, "Update programs");
         final long PROGRAM_DURATION_MS = 60 * 1000;
         final String[] projection = { Programs._ID };
         applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
@@ -284,7 +291,7 @@
                 ResultUnit.MS);
         averages[1] = Stat.getAverage(applyBatchTimes);
 
-        // Query programs
+        Log.d(TAG, "Query programs");
         applyBatchTimes = MeasureTime.measure(QUERY_RUNS, new MeasureRun() {
             @Override
             public void run(int i) {
@@ -300,7 +307,7 @@
                 ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[2] = Stat.getAverage(applyBatchTimes);
 
-        // Query programs with selection
+        Log.d(TAG, "Query programs with selection");
         applyBatchTimes = MeasureTime.measure(QUERY_RUNS, new MeasureRun() {
             @Override
             public void run(int i) {
@@ -320,7 +327,7 @@
                 ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[3] = Stat.getAverage(applyBatchTimes);
 
-        // Query a program
+        Log.d(TAG, "Query a program");
         try (final Cursor cursor = mContentResolver.query(Programs.CONTENT_URI,
                 projection, null, null, null)) {
             applyBatchTimes = MeasureTime.measure(QUERY_RUNS, new MeasureRun() {
@@ -340,7 +347,7 @@
                 ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[4] = Stat.getAverage(applyBatchTimes);
 
-        // Delete programs
+        Log.d(TAG, "Delete programs");
         applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
             @Override
             public void run(int i) {
@@ -357,7 +364,7 @@
                 ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[5] = Stat.getAverage(applyBatchTimes);
 
-        // Delete channels
+        Log.d(TAG, "Delete channels");
         applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
             @Override
             public void run(int i) {
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
index 20c5c27..160c9b5 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
@@ -487,6 +487,7 @@
         charsKeyNames.add(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_CROPPING_TYPE.getName());
+        charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1.getName());