Merge "disable MediaPlayer2 tests" into pi-dev
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index e3b1916..cb3f51a 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -27,8 +27,6 @@
 import string
 import unicodedata
 
-CMD_DELAY = 1  # seconds
-
 
 class ItsSession(object):
     """Controls a device over adb to run ITS scripts.
@@ -204,10 +202,6 @@
 
         # TODO: Figure out why "--user 0" is needed, and fix the problem.
         _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
-        _run(('%s shell am start --user 0 '
-              'com.android.cts.verifier/.camera.its.ItsTestActivity '
-              '--activity-brought-to-front') % self.adb)
-        time.sleep(CMD_DELAY)
         _run(('%s shell am start-foreground-service --user 0 -t text/plain '
               '-a %s') % (self.adb, self.INTENT_START))
 
@@ -911,11 +905,13 @@
     Returns:
         Nothing.
     """
+    ACTIVITY_START_WAIT = 1.5 # seconds
     adb = "adb -s " + device_id
 
-    # Start ItsTestActivity to prevent flaky
+    # Start ItsTestActivity to receive test results
     cmd = "%s shell am start %s --activity-brought-to-front" % (adb, ItsSession.ITS_TEST_ACTIVITY)
     _run(cmd)
+    time.sleep(ACTIVITY_START_WAIT)
 
     # Validate/process results argument
     for scene in results:
@@ -943,6 +939,20 @@
         print "ITS command string might be too long! len:", len(cmd)
     _run(cmd)
 
+def adb_log(device_id, msg):
+    """Send a log message to adb logcat
+
+    Args:
+        device_id: The ID string of the adb device
+        msg: the message string to be send to logcat
+
+    Returns:
+        Nothing.
+    """
+    adb = "adb -s " + device_id
+    cmd = "%s shell log -p i -t \"ItsTestHost\" %s" % (adb, msg)
+    _run(cmd)
+
 def get_device_fingerprint(device_id):
     """ Return the Build FingerPrint of the device that the test is running on.
 
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 24878a4..61c4917 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -420,6 +420,7 @@
 
                 msg = "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0)
                 print msg
+                its.device.adb_log(device_id, msg)
                 msg_short = "%s %s [%.1fs]" % (retstr, testname, t1-t0)
                 if test_failed:
                     summary += msg_short + "\n"
@@ -450,7 +451,9 @@
                                           else ItsSession.RESULT_FAIL)
             results[scene][ItsSession.SUMMARY_KEY] = summary_path
 
-        print "Reporting ITS result to CtsVerifier"
+        msg = "Reporting ITS result to CtsVerifier"
+        print msg
+        its.device.adb_log(device_id, msg)
         if merge_result_switch:
             # results are modified by report_result
             results_backup = copy.deepcopy(results)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index 8007983..7fa4e1a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -405,10 +405,10 @@
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
+    public void onDestroy() {
         Log.d(TAG, "unregister ITS result receiver");
         unregisterReceiver(mResultsReceiver);
+        super.onDestroy();
     }
 
     @Override
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
index 6c00d08..1f8b024 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
@@ -73,7 +73,7 @@
     public CompatibilityTestSuite() {
         try {
             OptionSetter setter = new OptionSetter(this);
-            setter.setOptionValue("config-patterns", ".*.config");
+            setter.setOptionValue("config-patterns", ".*\\.config");
             setter.setOptionValue("skip-loading-config-jar", "true");
         } catch (ConfigurationException e) {
             // Should not happen
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
index c8deffd..e727d56 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
@@ -75,7 +75,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE.equals(intent.getAction())) {
-                long token =
+                final long token =
                         intent.getLongExtra(BasicAdminReceiver.EXTRA_NETWORK_LOGS_BATCH_TOKEN,
                                 FAKE_BATCH_TOKEN);
                 // Retrieve network logs.
@@ -94,7 +94,7 @@
     };
 
     private CountDownLatch mBatchCountDown;
-    private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
+    private final ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
     private int mBatchesRequested = 1;
 
     @Override
@@ -174,7 +174,7 @@
 
         // if DeviceAdminReceiver#onNetworkLogsAvailable() hasn't been triggered yet, wait for up to
         // 3 minutes per batch just in case
-        int timeoutMins = 3 * mBatchesRequested;
+        final int timeoutMins = 3 * mBatchesRequested;
         mBatchCountDown.await(timeoutMins, TimeUnit.MINUTES);
         LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mNetworkLogsReceiver);
         if (mBatchCountDown.getCount() > 0) {
@@ -187,7 +187,7 @@
         assertEquals("First event has the wrong id.", 0L, mNetworkEvents.get(0).getId());
         // For each of the real URLs we have two events: one DNS and one connect. Dummy requests
         // don't require DNS queries.
-        int eventsExpected =
+        final int eventsExpected =
                 Math.min(FULL_LOG_BATCH_SIZE * mBatchesRequested,
                         2 * LOGGED_URLS_LIST.length + dummyReqNo);
         verifyNetworkLogs(mNetworkEvents, eventsExpected);
@@ -279,10 +279,10 @@
 
     /** Quickly generate loads of events by repeatedly connecting to a local server. */
     private int generateDummyTraffic() throws IOException, InterruptedException {
-        ServerSocket serverSocket = new ServerSocket(0);
-        Thread serverThread = startDummyServer(serverSocket);
+        final ServerSocket serverSocket = new ServerSocket(0);
+        final Thread serverThread = startDummyServer(serverSocket);
 
-        int reqNo = makeDummyRequests(serverSocket.getLocalPort());
+        final int reqNo = makeDummyRequests(serverSocket.getLocalPort());
 
         serverSocket.close();
         serverThread.join();
@@ -306,29 +306,27 @@
     }
 
     private Thread startDummyServer(ServerSocket serverSocket) throws InterruptedException {
-        Thread serverThread = new Thread(() -> {
-            while (true) {
+        final Thread serverThread = new Thread(() -> {
+            while (!serverSocket.isClosed()) {
                 try {
-                    Socket socket = serverSocket.accept();
+                    final Socket socket = serverSocket.accept();
                     // Consume input.
-                    BufferedReader input =
+                    final BufferedReader input =
                             new BufferedReader(new InputStreamReader(socket.getInputStream()));
                     String line;
                     do {
                         line = input.readLine();
                     } while (line != null && !line.equals(""));
                     // Return minimum valid response.
-                    PrintStream output = new PrintStream(socket.getOutputStream());
+                    final PrintStream output = new PrintStream(socket.getOutputStream());
                     output.println("HTTP/1.0 200 OK");
                     output.println("Content-Length: 0");
                     output.println();
                     output.flush();
                     output.close();
                 } catch (IOException e) {
-                    if (mBatchCountDown.getCount() > 0) {
+                    if (!serverSocket.isClosed()) {
                         Log.w(TAG, "Failed to serve connection", e);
-                    } else {
-                        break;
                     }
                 }
             }
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java
index e4f5176..98e4b7a 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java
@@ -34,7 +34,7 @@
         if (execCommandAndFind(CMD_TOP, SYSTEM_SERVER) != null) {
             CLog.logAndDisplay(LogLevel.INFO, "stop server");
             getDevice().executeShellCommand("stop");
-            Thread.sleep(3000); // wait for 3 seconds to stop.
+            Thread.sleep(10000); // wait for 10 seconds to stop.
             assertTrue("system_server failed to stop",
                     !execCommandAndGet(CMD_TOP).contains(SYSTEM_SERVER));
         }
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
index a3751f5..c62d85e 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -27,7 +27,7 @@
         final String destArg = dest == null || dest.isEmpty() ? "" : "-p " + dest;
         final IncidentProto dump = getDump(IncidentProto.parser(), "incident " + destArg + " 2>/dev/null");
 
-        SystemPropertiesTest.verifySystemPropertiesProto(dump.getSystemProperties(), filterLevel, mCtsBuild);
+        SystemPropertiesTest.verifySystemPropertiesProto(dump.getSystemProperties(), filterLevel);
 
         StackTraceIncidentTest.verifyBackTraceProto(dump.getNativeTraces(), filterLevel);
         StackTraceIncidentTest.verifyBackTraceProto(dump.getHalTraces(), filterLevel);
diff --git a/hostsidetests/incident/src/com/android/server/cts/SystemPropertiesTest.java b/hostsidetests/incident/src/com/android/server/cts/SystemPropertiesTest.java
index 28e1886..8dc4722 100644
--- a/hostsidetests/incident/src/com/android/server/cts/SystemPropertiesTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/SystemPropertiesTest.java
@@ -24,18 +24,16 @@
 public class SystemPropertiesTest extends ProtoDumpTestCase {
     private static final String TAG = "SystemPropertiesTest";
 
-    static void verifySystemPropertiesProto(SystemPropertiesProto properties,
-            final int filterLevel, IBuildInfo ctsBuild) {
+    static void verifySystemPropertiesProto(SystemPropertiesProto properties, final int filterLevel) {
         // check local tagged field
         if (filterLevel == PRIVACY_LOCAL) {
             assertTrue(properties.getExtraPropertiesCount() > 0);
         } else {
             assertEquals(0, properties.getExtraPropertiesCount());
         }
-        // check explicit tagged field
-        assertEquals(filterLevel == PRIVACY_AUTO, properties.getDalvikVm().getHeapmaxfree().isEmpty());
-        // check automatic tagged field
-        assertEquals(ctsBuild.getBuildId(),
-            properties.getRo().getBuild().getVersion().getIncremental());
+        // check explicit tagged field, persist.sys.timezone is public prop.
+        assertEquals(filterLevel == PRIVACY_AUTO, properties.getPersist().getSysTimezone().isEmpty());
+        // check automatic tagged field, ro.build.display.id is public prop.
+        assertFalse(properties.getRo().getBuild().getDisplayId().isEmpty());
     }
 }
diff --git a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
index d878342..2e0d0a5 100644
--- a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
+++ b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
@@ -73,8 +73,8 @@
 
     public void testDevHwRandomPermissions() throws Exception {
         // This test asserts that, if present, /dev/hw_random must:
-        // 1. Be owned by UID root and GID system
-        // 2. Have file permissions 0440 (only readable, and only by owner and group). The reason
+        // 1. Be owned by UID root
+        // 2. Not allow any world read, write, or execute permissions. The reason
         //    for being not readable by all/other is to avoid apps reading from this device.
         //    Firstly, /dev/hw_random is not public API for apps. Secondly, apps might erroneously
         //    use the output of Hardware RNG as trusted random output. Android does not trust output
@@ -96,9 +96,9 @@
             fail("Unexpected output from " + command + ": \"" + output + "\"");
         }
         String[] outputWords = output.split("\\s");
-        assertEquals("Wrong file mode on " + HW_RNG_DEVICE, "cr--r-----", outputWords[0]);
+        assertEquals("Wrong device type on " + HW_RNG_DEVICE, "c", outputWords[0].substring(0, 1));
+        assertEquals("Wrong world file mode on " + HW_RNG_DEVICE, "---", outputWords[0].substring(7));
         assertEquals("Wrong owner of " + HW_RNG_DEVICE, "root", outputWords[2]);
-        assertEquals("Wrong group of " + HW_RNG_DEVICE, "system", outputWords[3]);
         assertEquals("Wrong device major on " + HW_RNG_DEVICE, "10,", outputWords[4]);
         assertEquals("Wrong device minor on " + HW_RNG_DEVICE, "183", outputWords[5]);
 
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index 6cada58..fbba092 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -155,6 +155,7 @@
             devicePlatFcFile = getDeviceFile(mDevice, cachedDevicePlatFcFiles,
                     "/system/etc/selinux/plat_file_contexts", "plat_file_contexts");
             if (mDevice.doesFileExist("/vendor/etc/selinux/nonplat_file_contexts")){
+                // Old nonplat_* naming can be present if a framework-only OTA was done.
                 deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
                         "/vendor/etc/selinux/nonplat_file_contexts", "nonplat_file_contexts");
             } else {
@@ -165,7 +166,7 @@
             devicePlatFcFile = getDeviceFile(mDevice, cachedDevicePlatFcFiles,
                     "/plat_file_contexts", "plat_file_contexts");
             deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
-                    "/nonplat_file_contexts", "nonplat_file_contexts");
+                    "/vendor_file_contexts", "vendor_file_contexts");
         }
     }
 
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 71d600a..809d682 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.WakeLockLevelEnum;
+import android.platform.test.annotations.RestrictedBuildTest;
 
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
@@ -228,6 +229,7 @@
         assertTrue("found uid " + uid, found);
     }
 
+    @RestrictedBuildTest
     public void testCpuTimePerUidFreq() throws Exception {
         StatsdConfig.Builder config = getPulledConfig();
         FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
new file mode 100644
index 0000000..0f2c0e8
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.accessibilityservice.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.utils.AsyncUtils;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.Debug;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.widget.Button;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+// Test that an AccessibilityService can display an accessibility overlay
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityOverlayTest {
+
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+    InstrumentedAccessibilityService mService;
+
+    @BeforeClass
+    public static void oneTimeSetUp() {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation
+                .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+        sUiAutomation.setServiceInfo(info);
+    }
+
+    @Before
+    public void setUp() {
+        mService = StubAccessibilityButtonService.enableSelf(sInstrumentation);
+    }
+
+    @After
+    public void tearDown() {
+        mService.runOnServiceSync(() -> mService.disableSelf());
+    }
+
+    @Test
+    public void testA11yServiceShowsOverlay_shouldAppear() throws Exception {
+        final Button button = new Button(mService);
+        button.setText("Button");
+        final String overlayTitle = "Overlay title";
+
+        sUiAutomation.executeAndWaitForEvent(() -> mService.runOnServiceSync(() -> {
+            final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+            params.width = WindowManager.LayoutParams.MATCH_PARENT;
+            params.height = WindowManager.LayoutParams.MATCH_PARENT;
+            params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+            params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+            params.setTitle(overlayTitle);
+            mService.getSystemService(WindowManager.class).addView(button, params);
+        }), (event) -> findOverlayWindow() != null, AsyncUtils.DEFAULT_TIMEOUT_MS);
+
+        assertTrue(TextUtils.equals(findOverlayWindow().getTitle(), overlayTitle));
+    }
+
+    private AccessibilityWindowInfo findOverlayWindow() {
+        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+        for (int i = 0; i < windows.size(); i++) {
+            AccessibilityWindowInfo window = windows.get(i);
+            if (window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
+                return window;
+            }
+        }
+        return null;
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
index 09328fa..53a70b3 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
@@ -1,3 +1,19 @@
+/*
+ * 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.accessibilityservice.cts;
 
 import android.accessibilityservice.AccessibilityService;
diff --git a/tests/autofillservice/res/layout/fat_activity.xml b/tests/autofillservice/res/layout/fat_activity.xml
index 9b4a8b6..2efd33c 100644
--- a/tests/autofillservice/res/layout/fat_activity.xml
+++ b/tests/autofillservice/res/layout/fat_activity.xml
@@ -17,6 +17,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/root"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:focusable="true"
@@ -136,10 +137,10 @@
 
     </LinearLayout>
 
-    <View
+    <view class="android.autofillservice.cts.FatActivity$MyView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:autofillHints="importantAmI">
-    </View>
+    </view>
 
 </LinearLayout>
diff --git a/tests/autofillservice/res/layout/list_item.xml b/tests/autofillservice/res/layout/list_item.xml
index d7f0875..50364a2 100644
--- a/tests/autofillservice/res/layout/list_item.xml
+++ b/tests/autofillservice/res/layout/list_item.xml
@@ -22,6 +22,5 @@
     android:gravity="center_vertical"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:minHeight="?android:attr/listPreferredItemHeightSmall"
-    android:background="#ffffffff">
+    android:minHeight="?android:attr/listPreferredItemHeightSmall">
 </TextView>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
index da684d6..8ec9a3e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
@@ -16,8 +16,6 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.getContext;
-
 import android.view.View;
 
 import org.junit.Before;
@@ -38,12 +36,10 @@
             new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class, false);
 
     protected LoginActivity mActivity;
-    protected boolean mCanPassKeys;
 
     @Before
     public void setActivity() {
         mActivity = mActivityRule.getActivity();
-        mCanPassKeys = !Helper.isAutofillWindowFullScreen(getContext());
     }
 
     /**
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
index 3becd9b..642e372 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
@@ -67,6 +67,8 @@
      */
     @Test
     public final void testTapLink_changeOrientationThenTapBack() throws Exception {
+        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+
         final int width = mUiBot.getDevice().getDisplayWidth();
         final int heigth = mUiBot.getDevice().getDisplayHeight();
         final int min = Math.min(width, heigth);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
index 2e964b1..451b002 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
@@ -21,6 +21,7 @@
 
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
 import android.content.IntentSender;
+import android.os.SystemClock;
 
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -43,6 +44,12 @@
         runShellCommand("cmd autofill set max_visible_datasets %s", sMaxDatasets);
     }
 
+    private static void sendKeyEvents(String keyCode) {
+        runShellCommand("input keyevent " + keyCode);
+        // TODO wait mechanism for window size change
+        SystemClock.sleep(200);
+    }
+
     @Test
     public void testFilter() throws Exception {
         final String aa = "Two A's";
@@ -99,10 +106,6 @@
 
     @Test
     public void testFilter_usingKeyboard() throws Exception {
-        if (!mCanPassKeys) {
-            return;
-        }
-
         final String aa = "Two A's";
         final String ab = "A and B";
         final String b = "Only B";
@@ -133,26 +136,26 @@
         mUiBot.assertDatasets(aa, ab, b);
 
         // Only two datasets start with 'a'
-        runShellCommand("input keyevent KEYCODE_A");
+        sendKeyEvents("KEYCODE_A");
         mUiBot.assertDatasets(aa, ab);
 
         // Only one dataset start with 'aa'
-        runShellCommand("input keyevent KEYCODE_A");
+        sendKeyEvents("KEYCODE_A");
         mUiBot.assertDatasets(aa);
 
         // Only two datasets start with 'a'
-        runShellCommand("input keyevent KEYCODE_DEL");
+        sendKeyEvents("KEYCODE_DEL");
         mUiBot.assertDatasets(aa, ab);
 
         // With no filter text all datasets should be shown
-        runShellCommand("input keyevent KEYCODE_DEL");
+        sendKeyEvents("KEYCODE_DEL");
         mUiBot.assertDatasets(aa, ab, b);
 
         // No dataset start with 'aaa'
         final MyAutofillCallback callback = mActivity.registerCallback();
-        runShellCommand("input keyevent KEYCODE_A");
-        runShellCommand("input keyevent KEYCODE_A");
-        runShellCommand("input keyevent KEYCODE_A");
+        sendKeyEvents("KEYCODE_A");
+        sendKeyEvents("KEYCODE_A");
+        sendKeyEvents("KEYCODE_A");
         callback.assertUiHiddenEvent(mActivity.getUsername());
         mUiBot.assertNoDatasets();
     }
@@ -461,11 +464,11 @@
         mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
 
         // All datasets start with 'a'
-        runShellCommand("input keyevent KEYCODE_A");
+        sendKeyEvents("KEYCODE_A");
         mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
 
         // Only the regex datasets should start with 'ab'
-        runShellCommand("input keyevent KEYCODE_B");
+        sendKeyEvents("KEYCODE_B");
         mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
index 56b5cff..567aeba 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
@@ -22,6 +22,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.app.assist.AssistStructure;
 import android.util.Log;
 import android.view.autofill.AutofillId;
@@ -77,6 +79,8 @@
 
     @Test
     public void testDoNotRestoreDuplicateAutofillIds() throws Exception {
+        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+
         enableService();
 
         sReplier.addResponse(new CannedFillResponse.Builder()
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
index b3e064e..8a53f4a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
@@ -25,10 +25,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.Context;
 import android.os.Bundle;
+import android.util.AttributeSet;
 import android.view.View;
 import android.widget.EditText;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 
 /**
  * An activity containing mostly widgets that should be removed from an auto-fill structure to
@@ -41,6 +44,7 @@
     static final String ID_INPUT_CONTAINER = "input_container";
     static final String ID_IMAGE = "image";
     static final String ID_IMPORTANT_IMAGE = "important_image";
+    static final String ID_ROOT = "root";
 
     static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
             "not_important_container_excluding_descendants";
@@ -63,6 +67,7 @@
     static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD =
             "not_important_container_mixed_descendants_grand_child";
 
+    private LinearLayout mRoot;
     private EditText mCaptcha;
     private EditText mInput;
     private ImageView mImage;
@@ -80,7 +85,7 @@
     private View mNotImportantContainerMixedDescendantsChild;
     private View mNotImportantContainerMixedDescendantsGrandChild;
 
-    private View mViewWithAutofillHints;
+    private MyView mViewWithAutofillHints;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -88,6 +93,7 @@
 
         setContentView(R.layout.fat_activity);
 
+        mRoot = findViewById(R.id.root);
         mCaptcha = findViewById(R.id.captcha);
         mInput = findViewById(R.id.input);
         mImage = findViewById(R.id.image);
@@ -114,10 +120,11 @@
         mNotImportantContainerMixedDescendantsGrandChild = findViewById(
                 R.id.not_important_container_mixed_descendants_grand_child);
 
-        mViewWithAutofillHints = findViewByAutofillHint(this, "importantAmI");
+        mViewWithAutofillHints = (MyView) findViewByAutofillHint(this, "importantAmI");
         assertThat(mViewWithAutofillHints).isNotNull();
 
         // Sanity check for importantForAutofill modes
+        assertThat(mRoot.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
         assertThat(mInput.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
         assertThat(mCaptcha.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
         assertThat(mImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
@@ -157,4 +164,18 @@
             v.visit(mInput);
         });
     }
+
+    /**
+     * Custom view that defines an autofill type so autofill hints are set on {@code ViewNode}.
+     */
+    public static class MyView extends View {
+        public MyView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @Override
+        public int getAutofillType() {
+            return AUTOFILL_TYPE_TEXT;
+        }
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
index 9e458fd4..28ecdf7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
@@ -30,9 +30,11 @@
 import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS;
 import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD;
 import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD;
-import static android.autofillservice.cts.Helper.assertNumberOfChildren;
+import static android.autofillservice.cts.FatActivity.ID_ROOT;
+import static android.autofillservice.cts.Helper.findNodeByAutofillHint;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.autofillservice.cts.Helper.findNodeByText;
+import static android.autofillservice.cts.Helper.getNumberNodes;
 import static android.autofillservice.cts.Helper.importantForAutofillAsString;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
@@ -43,7 +45,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
 
@@ -61,7 +62,7 @@
         new AutofillActivityTestRule<FatActivity>(FatActivity.class);
 
     private FatActivity mFatActivity;
-    private AssistStructure mStructure;
+    private ViewNode mRoot;
 
     @Before
     public void setActivity() {
@@ -69,7 +70,7 @@
     }
 
     @Test
-    public void testNoContainers() throws Exception {
+    public void testAutomaticRequest() throws Exception {
         // Set service.
         enableService();
 
@@ -79,13 +80,22 @@
         // Trigger auto-fill.
         mFatActivity.onInput((v) -> v.requestFocus());
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        mStructure = fillRequest.structure;
         mUiBot.assertNoDatasetsEver();
 
-        // TODO: should only have X children, but there is an extra
-        // TextView that's probably coming from the title. For now we're just ignoring it, but
-        // ideally we should change the .xml to exclude it.
-        assertNumberOfChildren(fillRequest.structure, 9);
+        mRoot = findNodeByResourceId(fillRequest.structure, ID_ROOT);
+        /*
+         * Should have 8 nodes:
+         *
+         * 1. root
+         * 2. important_image
+         * 3. label without id but marked as important
+         * 4. input_container
+         * 5. input
+         * 6. important_container_excluding_descendants
+         * 7. not_important_container_mixed_descendants_child
+         * 8. view (My View) without id but with hints
+         */
+        assertThat(getNumberNodes(mRoot)).isEqualTo(8);
 
         // Should not have ImageView...
         assertThat(findNodeByResourceId(fillRequest.structure, ID_IMAGE)).isNull();
@@ -108,28 +118,31 @@
         assertThat(input.getIdEntry()).isEqualTo(ID_INPUT);
 
         // Make sure a non-important container can exclude descendants
-        assertThat(findNodeByResourceId(fillRequest.structure,
+        assertThat(findNodeByResourceId(mRoot,
                 ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS)).isNull();
-        assertThat(findNodeByResourceId(fillRequest.structure,
+        assertThat(findNodeByResourceId(mRoot,
                 ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD)).isNull();
-        assertThat(findNodeByResourceId(fillRequest.structure,
+        assertThat(findNodeByResourceId(mRoot,
                 ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD)).isNull();
 
         // Make sure an important container can exclude descendants
         assertNodeExists(ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS,
                 IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
-        assertThat(findNodeByResourceId(fillRequest.structure,
+        assertThat(findNodeByResourceId(mRoot,
                 ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD)).isNull();
-        assertThat(findNodeByResourceId(fillRequest.structure,
+        assertThat(findNodeByResourceId(mRoot,
                 ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD)).isNull();
 
         // Make sure an intermediary descendant can be excluded
-        assertThat(findNodeByResourceId(fillRequest.structure,
+        assertThat(findNodeByResourceId(mRoot,
                 ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS)).isNull();
         assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD,
                 IMPORTANT_FOR_AUTOFILL_YES);
-        assertThat(findNodeByResourceId(fillRequest.structure,
+        assertThat(findNodeByResourceId(mRoot,
                 ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD)).isNull();
+
+        // Make sure entry with hints is always shown
+        assertThat(findNodeByAutofillHint(mRoot, "importantAmI")).isNotNull();
     }
 
     @Test
@@ -143,13 +156,31 @@
         // Trigger autofill.
         mFatActivity.onInput((v) -> mFatActivity.getAutofillManager().requestAutofill(v));
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        mStructure = fillRequest.structure;
         mUiBot.assertNoDatasetsEver();
 
-        // TODO: should only have X children, but there is an extra
-        // TextView that's probably coming from the title. For now we're just ignoring it, but
-        // ideally we should change the .xml to exclude it.
-        assertNumberOfChildren(fillRequest.structure, 28);
+        mRoot = findNodeByResourceId(fillRequest.structure, ID_ROOT);
+        /*
+         * Should have 18 nodes:
+         * 1. root
+         * 2. layout without id
+         * 3. label without id
+         * 4. input_container
+         * 5. input
+         * 6. captcha
+         * 7. image
+         * 8. important_image
+         * 9. not_important_container_excluding_descendants
+         * 10.not_important_container_excluding_descendants_child
+         * 11.not_important_container_excluding_descendants_grand_child
+         * 12.important_container_excluding_descendants
+         * 13.important_container_excluding_descendants_child
+         * 14.important_container_excluding_descendants_grand_child
+         * 15.not_important_container_mixed_descendants
+         * 16.not_important_container_mixed_descendants_child
+         * 17.not_important_container_mixed_descendants_grand_child
+         * 18.view (My View) without id but with hints
+         */
+        assertThat(getNumberNodes(mRoot)).isEqualTo(18);
 
         // Assert all nodes are present
         assertNodeExists(ID_IMAGE, IMPORTANT_FOR_AUTOFILL_NO);
@@ -186,15 +217,18 @@
                 IMPORTANT_FOR_AUTOFILL_YES);
         assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD,
                 IMPORTANT_FOR_AUTOFILL_NO);
+        assertNode(findNodeByAutofillHint(mRoot, "importantAmI"), IMPORTANT_FOR_AUTOFILL_AUTO);
     }
 
     private ViewNode assertNodeExists(String resourceId, int expectedImportantForAutofill) {
-        final ViewNode node = findNodeByResourceId(mStructure, resourceId);
+        assertWithMessage("root node not set").that(mRoot).isNotNull();
+        final ViewNode node = findNodeByResourceId(mRoot, resourceId);
+        assertWithMessage("no node with resource id '%s'", resourceId).that(node).isNotNull();
         return assertNode(node, resourceId, expectedImportantForAutofill);
     }
 
     private ViewNode assertNodeWithTextExists(String text, int expectedImportantForAutofill) {
-        final ViewNode node = findNodeByText(mStructure, text);
+        final ViewNode node = findNodeByText(mRoot, text);
         return assertNode(node, text, expectedImportantForAutofill);
     }
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
index e559617..4196870 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
@@ -412,8 +412,9 @@
         mUiBot.pressBack(); // dismiss autofill
         mUiBot.pressBack(); // dismiss keyboard
         mUiBot.pressBack(); // dismiss task
-        assertThat(mActivity.getWindow().getDecorView().hasWindowFocus()).isTrue();
         mUiBot.assertShownByRelativeId(ID_USERNAME);
+        assertWithMessage("root window has no focus")
+                .that(mActivity.getWindow().getDecorView().hasWindowFocus()).isTrue();
 
         // ...and trigger save
         // Set credentials...
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index c3af0b1..0a6cfc7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -137,6 +137,10 @@
         return id.equals(node.getText());
     };
 
+    private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> {
+        return hasHint(node.getAutofillHints(), id);
+    };
+
     private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
         final String className = node.getClassName();
         if (!className.equals("android.webkit.WebView")) return false;
@@ -315,6 +319,14 @@
     }
 
     /**
+     * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
+     * found.
+     */
+    static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
+        return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
+    }
+
+    /**
      * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
      * not found.
      */
@@ -644,7 +656,7 @@
     }
 
     /**
-     * Gets the total number of nodes in an structure, including all descendants.
+     * Gets the total number of nodes in an structure.
      */
     static int getNumberNodes(AssistStructure structure) {
         int count = 0;
@@ -660,7 +672,7 @@
     /**
      * Gets the total number of nodes in an node, including all descendants and the node itself.
      */
-    private static int getNumberNodes(ViewNode node) {
+    static int getNumberNodes(ViewNode node) {
         int count = 1;
         final int childrenSize = node.getChildCount();
         if (childrenSize > 0) {
@@ -730,13 +742,20 @@
     }
 
     /**
-     * Check if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi
+     * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi.
      */
     public static boolean isAutofillWindowFullScreen(Context context) {
         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
     }
 
     /**
+     * Checks if screen orientation can be changed.
+     */
+    public static boolean isRotationSupported(Context context) {
+        return !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
+    /**
      * Uses Shell command to get the Autofill logging level.
      */
     public static String getLoggingLevel() {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 2457b19..475abc5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -30,6 +30,7 @@
 import static android.autofillservice.cts.Helper.assertValue;
 import static android.autofillservice.cts.Helper.dumpStructure;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.Helper.isAutofillWindowFullScreen;
 import static android.autofillservice.cts.Helper.setUserComplete;
 import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_CLASS;
 import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_PACKAGE;
@@ -365,6 +366,7 @@
         // TODO: currently disabled because the screenshot contains elements external to the
         // activity that can change (for exmaple, clock), which causes flakiness to the test.
         final boolean compareBitmaps = false;
+        final boolean pickerAndViewBoundsMatches = !isAutofillWindowFullScreen(mContext);
 
         // Set service.
         enableService();
@@ -389,8 +391,10 @@
         Log.v(TAG,
                 "Username1 at " + usernameBoundaries1 + "; picker at " + usernamePickerBoundaries1);
         // TODO(b/37566627): assertions below might be too aggressive - use range instead?
-        assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
-        assertThat(usernamePickerBoundaries1.left).isEqualTo(usernameBoundaries1.left);
+        if (pickerAndViewBoundsMatches) {
+            assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
+            assertThat(usernamePickerBoundaries1.left).isEqualTo(usernameBoundaries1.left);
+        }
 
         // Move to password
         final Rect passwordBoundaries1 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
@@ -401,8 +405,10 @@
         Log.v(TAG,
                 "Password1 at " + passwordBoundaries1 + "; picker at " + passwordPickerBoundaries1);
         // TODO(b/37566627): assertions below might be too aggressive - use range instead?
-        assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
-        assertThat(passwordPickerBoundaries1.left).isEqualTo(passwordBoundaries1.left);
+        if (pickerAndViewBoundsMatches) {
+            assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
+            assertThat(passwordPickerBoundaries1.left).isEqualTo(passwordBoundaries1.left);
+        }
 
         // Then back to username
         final Rect usernameBoundaries2 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
@@ -2422,8 +2428,8 @@
 
         // Make sure all datasets are shown.
         // TODO: improve assertDatasets() so it supports scrolling, and assert all of them are
-        // shown
-        mUiBot.assertDatasets("DS-1", "DS-2", "DS-3");
+        // shown. In fullscreen there are 4 items, otherwise there are 3 items.
+        mUiBot.assertDatasetsContains("DS-1", "DS-2", "DS-3");
 
         // TODO: once it supports scrolling, selects the last dataset and asserts it's filled.
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
index d9f0a81..3ea8f36 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
@@ -33,6 +33,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.app.PendingIntent;
 import android.app.assist.AssistStructure;
 import android.content.Intent;
@@ -123,6 +125,8 @@
 
     @Test
     public void testDatasetAuthResponseWhileAutofilledAppIsLifecycled() throws Exception {
+        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+
         // Set service.
         enableService();
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
index 46ca5f6..b24b56d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
@@ -1139,6 +1139,7 @@
      * Tests scenario where service explicitly indicates which button is used to save.
      */
     private void explicitySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
+        final boolean testBitmap = false;
         startActivity();
         mActivity.setAutoCommit(false);
         mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
@@ -1164,7 +1165,10 @@
         // Take a screenshot to make sure button doesn't disappear.
         final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
         assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT");
-        final Bitmap screenshotBefore = mActivity.takeScreenshot(mActivity.mCommit);
+        // Disable unnecessary screenshot tests as takeScreenshot() fails on some device.
+
+        final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
+                : null;
 
         // Save it...
         mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
@@ -1174,13 +1178,16 @@
         // Make sure save button is showning (it was removed on earlier versions of the feature)
         final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
         assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT");
-        final Bitmap screenshotAfter = mActivity.takeScreenshot(mActivity.mCommit);
+        final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
+                : null;
 
         // ... and assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
 
-        Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
+        if (testBitmap) {
+            Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
+        }
     }
 
     @Override
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 31ca017..17819fe 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -68,6 +68,7 @@
     private static final String TAG = "AutoFillCtsUiBot";
 
     private static final String RESOURCE_ID_DATASET_PICKER = "autofill_dataset_picker";
+    private static final String RESOURCE_ID_DATASET_HEADER = "autofill_dataset_header";
     private static final String RESOURCE_ID_SAVE_SNACKBAR = "autofill_save";
     private static final String RESOURCE_ID_SAVE_ICON = "autofill_save_icon";
     private static final String RESOURCE_ID_SAVE_TITLE = "autofill_save_title";
@@ -95,6 +96,8 @@
 
     static final BySelector DATASET_PICKER_SELECTOR = By.res("android", RESOURCE_ID_DATASET_PICKER);
     private static final BySelector SAVE_UI_SELECTOR = By.res("android", RESOURCE_ID_SAVE_SNACKBAR);
+    private static final BySelector DATASET_HEADER_SELECTOR =
+            By.res("android", RESOURCE_ID_DATASET_HEADER);
 
     private static final boolean DONT_DUMP_ON_ERROR = false;
     private static final boolean DUMP_ON_ERROR = true;
@@ -162,7 +165,7 @@
     }
 
     /**
-     * Asserts the dataset chooser is shown and contains the given datasets.
+     * Asserts the dataset chooser is shown and contains exactly the given datasets.
      *
      * @return the dataset picker object.
      */
@@ -174,7 +177,20 @@
     }
 
     /**
+     * Asserts the dataset chooser is shown and contains the given datasets.
+     *
+     * @return the dataset picker object.
+     */
+    UiObject2 assertDatasetsContains(String...names) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
+                .containsAllIn(Arrays.asList(names)).inOrder();
+        return picker;
+    }
+
+    /**
      * Asserts the dataset chooser is shown and contains the given datasets, header, and footer.
+     * <p>In fullscreen, header view is not under R.id.autofill_dataset_picker.
      *
      * @return the dataset picker object.
      */
@@ -183,7 +199,15 @@
         final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
         final List<String> expectedChild = new ArrayList<>();
         if (header != null) {
-            expectedChild.add(header);
+            if (Helper.isAutofillWindowFullScreen(mContext)) {
+                final UiObject2 headerView = waitForObject(DATASET_HEADER_SELECTOR,
+                        UI_DATASET_PICKER_TIMEOUT);
+                assertWithMessage("fullscreen wrong dataset header")
+                        .that(getChildrenAsText(headerView))
+                        .containsExactlyElementsIn(Arrays.asList(header)).inOrder();
+            } else {
+                expectedChild.add(header);
+            }
         }
         expectedChild.addAll(Arrays.asList(names));
         if (footer != null) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
index 8028f1d..5fd507f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
@@ -33,6 +33,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.app.assist.AssistStructure.ViewNode;
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
@@ -107,7 +109,7 @@
 
     @Test
     public void testAutofillAsync() throws Exception {
-        if (mCompatMode) return;
+        skipTestOnCompatMode();
 
         autofillTest(false);
     }
@@ -288,7 +290,7 @@
 
     @Test
     public void testAutofillManuallyOneDataset() throws Exception {
-        if (mCompatMode) return; // TODO(b/73557072): not supported yet
+        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
 
         // Set service.
         enableService();
@@ -323,7 +325,7 @@
     }
 
     private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
-        if (mCompatMode) return; // TODO(b/73557072): not supported yet
+        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
 
         // Set service.
         enableService();
@@ -752,12 +754,15 @@
      * Asserts the dataset picker is properly displayed in a give line.
      */
     protected void assertDatasetShown(Line line, String... expectedDatasets) throws Exception {
+        boolean autofillViewBoundsMatches = !Helper.isAutofillWindowFullScreen(mContext);
         final Rect pickerBounds = mUiBot.assertDatasets(expectedDatasets).getVisibleBounds();
         final Rect fieldBounds = line.getAbsCoordinates();
-        assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
-                fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
-        assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s", pickerBounds,
-                fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
+        if (autofillViewBoundsMatches) {
+            assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
+                    fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
+            assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s",
+                    pickerBounds, fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
+        }
     }
 
     protected void assertLabel(ViewNode node, String expectedValue) {
@@ -774,4 +779,9 @@
         assertThat(urlBar.getWebDomain()).isNull();
         assertThat(urlBar.getWebScheme()).isNull();
     }
+
+
+    private void skipTestOnCompatMode() {
+        assumeTrue("test not applicable when on compat mode", !mCompatMode);
+    }
 }
diff --git a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
index 6fca9ad..2e42e21 100644
--- a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
+++ b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
@@ -50,6 +50,9 @@
         if (!isBackupSupported()) {
             return;
         }
+        // Launch the main activity so the app qualifies for backup.
+        createTestFileOfSize(BACKUP_APP_NAME, 1);
+
         String separator = markLogcat();
         exec("bmgr backupnow " + BACKUP_APP_NAME);
         waitForLogcat(TIMEOUT_SECONDS, separator,
diff --git a/tests/framework/base/activitymanager/AndroidManifest.xml b/tests/framework/base/activitymanager/AndroidManifest.xml
index 6227834..a0d302d 100644
--- a/tests/framework/base/activitymanager/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -74,6 +74,9 @@
         <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$SingleTopActivity"
                   android:launchMode="singleTop" />
 
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$ConfigChangeHandlingActivity"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density" />
+
         <activity android:name="android.server.am.StartActivityTests$TestActivity2" />
 
     </application>
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
index a36079c..68be619 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
@@ -212,6 +212,11 @@
         performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_PAUSE);
     }
 
+    @Test
+    public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
+        performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_STOP);
+    }
+
     private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
         // Make sure home activity is visible.
         launchHomeActivity();
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
index 070fce1..c912e6e 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
@@ -2,12 +2,16 @@
 
 import static android.server.am.StateLogger.log;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback
+        .ON_MULTI_WINDOW_MODE_CHANGED;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
 
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
 import android.server.am.ActivityManagerTestBase;
@@ -30,6 +34,12 @@
     static final String EXTRA_FINISH_IN_ON_RESUME = "finish_in_on_resume";
     static final String EXTRA_FINISH_AFTER_RESUME = "finish_after_resume";
 
+    static final ComponentName CALLBACK_TRACKING_ACTIVITY =
+            getComponentName(CallbackTrackingActivity.class);
+
+    static final ComponentName CONFIG_CHANGE_HANDLING_ACTIVITY =
+            getComponentName(ConfigChangeHandlingActivity.class);
+
     final ActivityTestRule mFirstActivityTestRule = new ActivityTestRule(FirstActivity.class,
             true /* initialTouchMode */, false /* launchActivity */);
 
@@ -53,6 +63,10 @@
     final ActivityTestRule mSingleTopActivityTestRule = new ActivityTestRule(
             SingleTopActivity.class, true /* initialTouchMode */, false /* launchActivity */);
 
+    final ActivityTestRule mConfigChangeHandlingActivityTestRule = new ActivityTestRule(
+            ConfigChangeHandlingActivity.class, true /* initialTouchMode */,
+            false /* launchActivity */);
+
     private final ActivityLifecycleMonitor mLifecycleMonitor = ActivityLifecycleMonitorRegistry
             .getInstance();
     private static LifecycleLog mLifecycleLog;
@@ -149,6 +163,11 @@
             super.onNewIntent(intent);
             mLifecycleLog.onActivityCallback(this, ON_NEW_INTENT);
         }
+
+        @Override
+        public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
+            mLifecycleLog.onActivityCallback(this, ON_MULTI_WINDOW_MODE_CHANGED);
+        }
     }
 
     /**
@@ -195,4 +214,12 @@
             }
         }
     }
+
+    // Config change handling activity
+    public static class ConfigChangeHandlingActivity extends CallbackTrackingActivity {
+    }
+
+    static ComponentName getComponentName(Class<? extends Activity> activity) {
+        return new ComponentName(InstrumentationRegistry.getContext(), activity);
+    }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
index da47687..fa9894f 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
@@ -1,8 +1,14 @@
 package android.server.am.lifecycle;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.UiDeviceUtils.pressBackButton;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback
+        .ON_MULTI_WINDOW_MODE_CHANGED;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
@@ -11,8 +17,6 @@
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
-import static android.server.am.ActivityManagerState.STATE_STOPPED;
-import static android.server.am.UiDeviceUtils.pressBackButton;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
@@ -598,4 +602,81 @@
         LifecycleVerifier.assertSequenceMatchesOneOf(SingleTopActivity.class, getLifecycleLog(),
                 Arrays.asList(expectedSequence, extraPauseSequence), "newIntent");
     }
+
+    @Test
+    public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
+        // Launch a singleTop activity
+        final Activity testActivity =
+                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+
+        // Wait for the activity to resume
+        waitAndAssertActivityStates(state(testActivity, ON_RESUME));
+        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                true /* includeCallbacks */);
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Wait for the activity to pause
+        final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
+                Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
+                        ON_POST_CREATE, ON_RESUME, ON_MULTI_WINDOW_MODE_CHANGED, ON_PAUSE);
+        waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
+
+        // Verify that the activity was relaunched and received multi-window mode change
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                expectedEnterSequence, "moveToSplitScreen");
+
+        // Exit split-screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Wait for the activity to resume
+        final List<LifecycleLog.ActivityCallback> expectedExitSequence =
+                Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
+                        ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_MULTI_WINDOW_MODE_CHANGED,
+                        ON_RESUME);
+        waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
+
+        // Verify that the activity was relaunched and received multi-window mode change
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                expectedExitSequence, "moveFromSplitScreen");
+    }
+
+    @Test
+    public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
+        // Launch a singleTop activity
+        final Activity testActivity =
+                mConfigChangeHandlingActivityTestRule.launchActivity(new Intent());
+
+        // Wait for the activity to resume
+        waitAndAssertActivityStates(state(testActivity, ON_RESUME));
+        LifecycleVerifier.assertLaunchSequence(ConfigChangeHandlingActivity.class,
+                getLifecycleLog(), true /* includeCallbacks */);
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Wait for the activity to pause
+        waitAndAssertActivityStates(state(testActivity, ON_PAUSE));
+
+        // Verify that the activity was relaunched and received multi-window mode change
+        LifecycleVerifier.assertSequence(ConfigChangeHandlingActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_PAUSE), "moveToSplitScreen");
+
+        // Exit split-screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Wait for the activity to resume
+        waitAndAssertActivityStates(state(testActivity, ON_RESUME));
+
+        // Verify that the activity was relaunched and received multi-window mode change
+        LifecycleVerifier.assertSequence(ConfigChangeHandlingActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_RESUME), "moveFromSplitScreen");
+    }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
index 3a8705a..da8296c 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
@@ -34,7 +34,8 @@
         ON_DESTROY,
         ON_ACTIVITY_RESULT,
         ON_POST_CREATE,
-        ON_NEW_INTENT
+        ON_NEW_INTENT,
+        ON_MULTI_WINDOW_MODE_CHANGED
     }
 
     /**
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
index 0809f3e..5a6b0be 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
@@ -399,7 +399,8 @@
     }
 
     protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) {
-        final int taskId = getActivityTaskId(activityName);
+        mAmWmState.computeState(activityName);
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
         mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
@@ -408,7 +409,8 @@
     }
 
     protected void moveActivityToStack(ComponentName activityName, int stackId) {
-        final int taskId = getActivityTaskId(activityName);
+        mAmWmState.computeState(activityName);
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
         final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
         executeShellCommand(cmd);
 
@@ -419,7 +421,8 @@
 
     protected void resizeActivityTask(
             ComponentName activityName, int left, int top, int right, int bottom) {
-        final int taskId = getActivityTaskId(activityName);
+        mAmWmState.computeState(activityName);
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
         final String cmd = "am task resize "
                 + taskId + " " + left + " " + top + " " + right + " " + bottom;
         executeShellCommand(cmd);
@@ -452,25 +455,6 @@
         }
     }
 
-    @Deprecated
-    protected int getActivityTaskId(final ComponentName activityName) {
-        final String windowName = getWindowName(activityName);
-        final String output = executeShellCommand(AM_STACK_LIST);
-        final Pattern activityPattern = Pattern.compile("(.*) " + windowName + " (.*)");
-        for (final String line : output.split("\\n")) {
-            final Matcher matcher = activityPattern.matcher(line);
-            if (matcher.matches()) {
-                for (String word : line.split("\\s+")) {
-                    if (word.startsWith(TASK_ID_PREFIX)) {
-                        final String withColon = word.split("=")[1];
-                        return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
-                    }
-                }
-            }
-        }
-        return -1;
-    }
-
     protected boolean supportsVrMode() {
         return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
     }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
index 8708b83..de3235c 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
@@ -62,6 +62,7 @@
 import com.android.cts.mockime.MockImeSession;
 
 import org.junit.BeforeClass;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -84,7 +85,15 @@
     }
 
     @BeforeClass
-    public static void checkNavigationBar() throws Exception {
+    public static void initializeNavigationBarInfo() throws Exception {
+        // Make sure that NavigationBarInfo is initialized before
+        // EndToEndImeTestBase#showStateInitializeActivity().
+        NavigationBarInfo.getInstance();
+    }
+
+    // TODO(b/37502066): Merge this back to initializeNavigationBarInfo() once b/37502066 is fixed.
+    @Before
+    public void checkNavigationBar() throws Exception {
         assumeTrue("This test does not make sense if there is no navigation bar",
                 NavigationBarInfo.getInstance().hasBottomNavigationBar());
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
index 48710ec..add3237 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
@@ -23,19 +23,15 @@
 import android.support.test.InstrumentationRegistry;
 
 import org.junit.Before;
-import org.junit.BeforeClass;
 
 public class EndToEndImeTestBase {
-
-    @BeforeClass
-    public static void assumeFeatureInputMethod() {
+    @Before
+    public void showStateInitializeActivity() {
+        // TODO(b/37502066): Move this back to @BeforeClass once b/37502066 is fixed.
         assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
                 InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_INPUT_METHODS));
-    }
 
-    @Before
-    public void showStateInitializeActivity() {
         final Intent intent = new Intent()
                 .setAction(Intent.ACTION_MAIN)
                 .setClass(InstrumentationRegistry.getTargetContext(), StateInitializeActivity.class)
diff --git a/tests/tests/appwidget/Android.mk b/tests/tests/appwidget/Android.mk
index b6243fc..dea9c46 100644
--- a/tests/tests/appwidget/Android.mk
+++ b/tests/tests/appwidget/Android.mk
@@ -24,7 +24,7 @@
     $(call all-java-files-under, common/src)
 
 LOCAL_PACKAGE_NAME := CtsAppWidgetTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_SDK_VERSION := test_current
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     mockito-target-minus-junit4 \
diff --git a/tests/tests/appwidget/AndroidTest.xml b/tests/tests/appwidget/AndroidTest.xml
index cc4fe42..e64123c 100644
--- a/tests/tests/appwidget/AndroidTest.xml
+++ b/tests/tests/appwidget/AndroidTest.xml
@@ -43,6 +43,7 @@
 
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="hidden-api-checks" value="false" />
         <option name="package" value="android.appwidget.cts" />
         <option name="runtime-hint" value="9m30s" />
     </test>
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
index 5c4f517..08777c1 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
@@ -204,7 +203,7 @@
         final Bundle secondOptions;
 
         // Create a host and start listening.
-        AppWidgetHost host = spy(new AppWidgetHost(getInstrumentation().getTargetContext(), 0));
+        AppWidgetHost host = new AppWidgetHost(getInstrumentation().getTargetContext(), 0);
         host.deleteHost();
         host.startListening();
 
@@ -314,8 +313,7 @@
         int secondAppWidgetId = 0;
 
         // Create a host and start listening.
-        AppWidgetHost host = spy(new AppWidgetHost(
-                getInstrumentation().getTargetContext(), 0));
+        AppWidgetHost host = new AppWidgetHost(getInstrumentation().getTargetContext(), 0);
         host.deleteHost();
         host.startListening();
 
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index 6df9f8e..7d88ec2 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -54,7 +54,8 @@
     ndkaudio \
     testng \
     truth-prebuilt \
-    mockito-target-minus-junit4
+    mockito-target-minus-junit4 \
+    androidx.heifwriter_heifwriter \
 
 LOCAL_JNI_SHARED_LIBRARIES := \
     libaudio_jni \
diff --git a/tests/tests/media/res/raw/heifwriter_input.heic b/tests/tests/media/res/raw/heifwriter_input.heic
new file mode 100644
index 0000000..ceaff92
--- /dev/null
+++ b/tests/tests/media/res/raw/heifwriter_input.heic
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java b/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java
index a7ad33b..60f2d21 100644
--- a/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java
+++ b/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java
@@ -24,8 +24,14 @@
 import android.media.MediaPlayer;
 import android.media.audiofx.AudioEffect;
 import android.media.audiofx.DynamicsProcessing;
+import android.media.audiofx.DynamicsProcessing.BandBase;
+import android.media.audiofx.DynamicsProcessing.BandStage;
 import android.media.audiofx.DynamicsProcessing.Channel;
 import android.media.audiofx.DynamicsProcessing.Eq;
+import android.media.audiofx.DynamicsProcessing.EqBand;
+import android.media.audiofx.DynamicsProcessing.Limiter;
+import android.media.audiofx.DynamicsProcessing.Mbc;
+import android.media.audiofx.DynamicsProcessing.MbcBand;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -35,7 +41,6 @@
     private DynamicsProcessing mDP;
 
     private static final int MIN_CHANNEL_COUNT = 1;
-    private static final int TEST_CHANNEL = 0;
     private static final float EPSILON = 0.00001f;
     private static final int DEFAULT_VARIANT =
             DynamicsProcessing.VARIANT_FAVOR_FREQUENCY_RESOLUTION;
@@ -49,6 +54,12 @@
     private static final float DEFAULT_FRAME_DURATION = 9.5f;
     private static final float DEFAULT_INPUT_GAIN = -12.5f;
 
+    private static final int TEST_CHANNEL_COUNT = 2;
+    private static final float TEST_GAIN1 = 12.1f;
+    private static final float TEST_GAIN2 = -2.8f;
+    private static final int TEST_CHANNEL_INDEX = 0;
+    private static final int TEST_BAND_INDEX = 0;
+
     //-----------------------------------------------------------------
     // DynamicsProcessing tests:
     //----------------------------------
@@ -129,7 +140,7 @@
             final int channelCount = mDP.getChannelCount();
             assertTrue("unexpected channel count", channelCount >= MIN_CHANNEL_COUNT);
 
-            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL);
+            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
 
             final float inputGain = channel.getInputGain();
             assertEquals("inputGain is different", DEFAULT_INPUT_GAIN, inputGain, EPSILON);
@@ -145,7 +156,7 @@
         try {
             createDefaultEffect();
 
-            DynamicsProcessing.Eq eq = mDP.getPreEqByChannelIndex(TEST_CHANNEL);
+            DynamicsProcessing.Eq eq = mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
 
             final boolean inUse = eq.isInUse();
             assertEquals("inUse is different", DEFAULT_PREEQ_IN_USE, inUse);
@@ -165,7 +176,7 @@
         try {
             createDefaultEffect();
 
-            DynamicsProcessing.Mbc mbc = mDP.getMbcByChannelIndex(TEST_CHANNEL);
+            DynamicsProcessing.Mbc mbc = mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
 
             final boolean inUse = mbc.isInUse();
             assertEquals("inUse is different", DEFAULT_MBC_IN_USE, inUse);
@@ -184,7 +195,7 @@
         try {
             createDefaultEffect();
 
-            DynamicsProcessing.Eq eq = mDP.getPostEqByChannelIndex(TEST_CHANNEL);
+            DynamicsProcessing.Eq eq = mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
 
             boolean inUse = eq.isInUse();
             assertEquals("inUse is different", DEFAULT_POSTEQ_IN_USE, inUse);
@@ -204,7 +215,7 @@
         try {
             createDefaultEffect();
 
-            DynamicsProcessing.Limiter limiter = mDP.getLimiterByChannelIndex(TEST_CHANNEL);
+            DynamicsProcessing.Limiter limiter = mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
 
             final boolean inUse = limiter.isInUse();
             assertEquals("inUse is different", DEFAULT_LIMITER_IN_USE, inUse);
@@ -214,10 +225,838 @@
     }
 
     //-----------------------------------------------------------------
-    // 2 - run and change parameters
+    // 2 - config builder tests
     //----------------------------------
 
-    //TODO: runtime change of parameters.
+    public void test2_0ConfigBasic() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues().build();
+
+        assertEquals("getVariant is different", DEFAULT_VARIANT,
+                config.getVariant());
+        assertEquals("isPreEqInUse is different", DEFAULT_PREEQ_IN_USE,
+                config.isPreEqInUse());
+        assertEquals("getPreEqBandCount is different", DEFAULT_PREEQ_BAND_COUNT,
+                config.getPreEqBandCount());
+        assertEquals("isMbcInUse is different", DEFAULT_MBC_IN_USE,
+                config.isMbcInUse());
+        assertEquals("getMbcBandCount is different", DEFAULT_MBC_BAND_COUNT,
+                config.getMbcBandCount());
+        assertEquals("isPostEqInUse is different", DEFAULT_POSTEQ_IN_USE,
+                config.isPostEqInUse());
+        assertEquals("getPostEqBandCount is different", DEFAULT_POSTEQ_BAND_COUNT,
+                config.getPostEqBandCount());
+        assertEquals("isLimiterInUse is different", DEFAULT_LIMITER_IN_USE,
+                config.isLimiterInUse());
+        assertEquals("getPreferredFrameDuration is different", DEFAULT_FRAME_DURATION,
+                config.getPreferredFrameDuration());
+    }
+
+    public void test2_1ConfigChannel() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
+
+        //per channel
+        Channel channel = config.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
+        //change some parameters
+        channel.setInputGain(TEST_GAIN1);
+
+        //get stages from channel
+        Eq preEq = channel.getPreEq();
+        EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
+        preEqBand.setGain(TEST_GAIN1);
+        channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
+
+        Mbc mbc = channel.getMbc();
+        MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
+        mbcBand.setPreGain(TEST_GAIN1);
+        channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
+
+        Eq postEq = channel.getPostEq();
+        EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
+        postEqBand.setGain(TEST_GAIN1);
+        channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
+
+        Limiter limiter = channel.getLimiter();
+        limiter.setPostGain(TEST_GAIN1);
+        channel.setLimiter(limiter);
+
+        config.setAllChannelsTo(channel);
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            Channel channelTest = new Channel(config.getChannelByChannelIndex(i));
+            assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
+                    channelTest.getInputGain(), EPSILON);
+
+            EqBand preEqBandTest = new EqBand(channelTest.getPreEqBand(TEST_BAND_INDEX));
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+            MbcBand mbcBandTest = new MbcBand(channelTest.getMbcBand(TEST_BAND_INDEX));
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+            EqBand postEqBandTest = new EqBand(channelTest.getPostEqBand(TEST_BAND_INDEX));
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+            Limiter limiterTest = new Limiter(channelTest.getLimiter());
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
+
+            ///changes per channelIndex
+            channelTest.setInputGain(TEST_GAIN2);
+            preEqBandTest.setGain(TEST_GAIN2);
+            channelTest.setPreEqBand(TEST_BAND_INDEX, preEqBandTest);
+            mbcBandTest.setPreGain(TEST_GAIN2);
+            channelTest.setMbcBand(TEST_BAND_INDEX, mbcBandTest);
+            postEqBandTest.setGain(TEST_GAIN2);
+            channelTest.setPostEqBand(TEST_BAND_INDEX, postEqBandTest);
+            limiterTest.setPostGain(TEST_GAIN2);
+            channelTest.setLimiter(limiterTest);
+            config.setChannelTo(i, channelTest);
+
+            assertEquals("inputGain is different in channel " + i, TEST_GAIN2,
+                    config.getInputGainByChannelIndex(i), EPSILON);
+
+            //get by module
+            Eq preEqTest = config.getPreEqByChannelIndex(i);
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN2, preEqTest.getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+            Mbc mbcTest = config.getMbcByChannelIndex(i);
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN2, mbcTest.getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+            Eq postEqTest = config.getPostEqByChannelIndex(i);
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN2, postEqTest.getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+            limiterTest = config.getLimiterByChannelIndex(i);
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN2, limiterTest.getPostGain(), EPSILON);
+        }
+    }
+
+    public void test2_2ConfigChannel_perStage() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
+
+        //Per Stage
+        config.setInputGainAllChannelsTo(TEST_GAIN1);
+
+        Eq preEq = config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
+        EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
+        preEqBand.setGain(TEST_GAIN1);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        config.setPreEqAllChannelsTo(preEq);
+
+        Mbc mbc = config.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
+        MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
+        mbcBand.setPreGain(TEST_GAIN1);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        config.setMbcAllChannelsTo(mbc);
+
+        Eq postEq = config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
+        EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
+        postEqBand.setGain(TEST_GAIN1);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        config.setPostEqAllChannelsTo(postEq);
+
+        Limiter limiter = config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
+        limiter.setPostGain(TEST_GAIN1);
+        config.setLimiterAllChannelsTo(limiter);
+
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            Channel channelTest = config.getChannelByChannelIndex(i);
+            assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
+                    channelTest.getInputGain(), EPSILON);
+
+            Eq preEqTest = new Eq(config.getPreEqByChannelIndex(i));
+            EqBand preEqBandTest = preEqTest.getBand(TEST_BAND_INDEX);
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+            Mbc mbcTest = new Mbc(config.getMbcByChannelIndex(i));
+            MbcBand mbcBandTest = mbcTest.getBand(TEST_BAND_INDEX);
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+            Eq postEqTest = new Eq(config.getPostEqByChannelIndex(i));
+            EqBand postEqBandTest = postEqTest.getBand(TEST_BAND_INDEX);
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+            Limiter limiterTest = new Limiter(config.getLimiterByChannelIndex(i));
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
+
+            //change by Stage
+            preEqBandTest.setGain(TEST_GAIN2);
+            preEqTest.setBand(TEST_BAND_INDEX, preEqBandTest);
+            config.setPreEqByChannelIndex(i, preEqTest);
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN2, config.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            mbcBandTest.setPreGain(TEST_GAIN2);
+            mbcTest.setBand(TEST_BAND_INDEX, mbcBandTest);
+            config.setMbcByChannelIndex(i, mbcTest);
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN2, config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            postEqBandTest.setGain(TEST_GAIN2);
+            postEqTest.setBand(TEST_BAND_INDEX, postEqBandTest);
+            config.setPostEqByChannelIndex(i, postEqTest);
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN2, config.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            limiterTest.setPostGain(TEST_GAIN2);
+            config.setLimiterByChannelIndex(i, limiterTest);
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN2, config.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
+
+    public void test2_3ConfigChannel_perBand() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
+
+        //Per Band
+        EqBand preEqBand = config.getPreEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+        preEqBand.setGain(TEST_GAIN1);
+        config.setPreEqBandAllChannelsTo(TEST_BAND_INDEX, preEqBand);
+
+        MbcBand mbcBand = config.getMbcBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+        mbcBand.setPreGain(TEST_GAIN1);
+        config.setMbcBandAllChannelsTo(TEST_BAND_INDEX, mbcBand);
+
+        EqBand postEqBand = config.getPostEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+        postEqBand.setGain(TEST_GAIN1);
+        config.setPostEqBandAllChannelsTo(TEST_BAND_INDEX, postEqBand);
+
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+
+            EqBand preEqBandTest = new EqBand(config.getPreEqBandByChannelIndex(i,
+                    TEST_BAND_INDEX));
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+            MbcBand mbcBandTest = new MbcBand(config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX));
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+            EqBand postEqBandTest = new EqBand(config.getPostEqBandByChannelIndex(i,
+                    TEST_BAND_INDEX));
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+            //change per Band
+            preEqBandTest.setGain(TEST_GAIN2);
+            config.setPreEqBandByChannelIndex(i, TEST_BAND_INDEX, preEqBandTest);
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN2, config.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            mbcBandTest.setPreGain(TEST_GAIN2);
+            config.setMbcBandByChannelIndex(i, TEST_BAND_INDEX, mbcBandTest);
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN2, config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            postEqBandTest.setGain(TEST_GAIN2);
+            config.setPostEqBandByChannelIndex(i, TEST_BAND_INDEX, postEqBandTest);
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN2, config.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+        }
+    }
+
+    public void test2_4Channel_perStage() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+
+        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+        channel.setInputGain(TEST_GAIN1);
+        assertEquals("channel gain is different", TEST_GAIN1, channel.getInputGain(), EPSILON);
+
+        //set by stage
+        Eq preEq = new Eq(channel.getPreEq());
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+        preEqBand.setGain(TEST_GAIN1);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        channel.setPreEq(preEq);
+        assertEquals("preEQBand gain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getPreEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+        preEqBand.setGain(TEST_GAIN2);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        channel.setPreEq(preEq);
+        assertEquals("preEQBand gain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getPreEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+        Mbc mbc = new Mbc(channel.getMbc());
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+        mbcBand.setPreGain(TEST_GAIN1);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        channel.setMbc(mbc);
+        assertEquals("mbcBand preGain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getMbc().getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+        mbcBand.setPreGain(TEST_GAIN2);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        channel.setMbc(mbc);
+        assertEquals("mbcBand preGain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getMbc().getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+        Eq postEq = new Eq(channel.getPostEq());
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+        postEqBand.setGain(TEST_GAIN1);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        channel.setPostEq(postEq);
+        assertEquals("postEqBand gain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getPostEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+        postEqBand.setGain(TEST_GAIN2);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        channel.setPostEq(postEq);
+        assertEquals("postEQBand gain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getPostEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+        Limiter limiter = new Limiter(channel.getLimiter());
+        limiter.setPostGain(TEST_GAIN1);
+        channel.setLimiter(limiter);
+        assertEquals("limiter gain is different",
+                TEST_GAIN1, channel.getLimiter().getPostGain(), EPSILON);
+        limiter.setPostGain(TEST_GAIN2);
+        channel.setLimiter(limiter);
+        assertEquals("limiter gain is different",
+                TEST_GAIN2, channel.getLimiter().getPostGain(), EPSILON);
+
+    }
+
+    public void test2_5Channel_perBand() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+
+        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+        channel.setInputGain(TEST_GAIN1);
+        assertEquals("channel gain is different", TEST_GAIN1, channel.getInputGain(), EPSILON);
+
+        //set by band
+        EqBand preEqBand = new EqBand(channel.getPreEqBand(TEST_BAND_INDEX));
+        preEqBand.setGain(TEST_GAIN1);
+        channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
+        assertEquals("preEQBand gain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getPreEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+        preEqBand.setGain(TEST_GAIN2);
+        channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
+        assertEquals("preEQBand gain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getPreEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+        MbcBand mbcBand = new MbcBand(channel.getMbcBand(TEST_BAND_INDEX));
+        mbcBand.setPreGain(TEST_GAIN1);
+        channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
+        assertEquals("mbcBand preGain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getMbcBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+        mbcBand.setPreGain(TEST_GAIN2);
+        channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
+        assertEquals("mbcBand preGain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getMbcBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+        EqBand postEqBand = new EqBand(channel.getPostEqBand(TEST_BAND_INDEX));
+        postEqBand.setGain(TEST_GAIN1);
+        channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
+        assertEquals("postEqBand gain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getPostEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+        postEqBand.setGain(TEST_GAIN2);
+        channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
+        assertEquals("postEqBand gain is different in band "+ TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getPostEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+    }
+
+    public void test2_6Eq() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int bandCount = 3;
+
+        Eq eq = new Eq(inUse, enabled, bandCount);
+        assertEquals("eq inUse is different", inUse, eq.isInUse());
+        assertEquals("eq enabled is different", enabled, eq.isEnabled());
+        assertEquals("eq bandCount is different", bandCount, eq.getBandCount());
+
+        //changes
+        eq.setEnabled(!enabled);
+        assertEquals("eq enabled is different", !enabled, eq.isEnabled());
+
+        //bands
+        for (int i = 0; i < bandCount; i++) {
+            final float frequency = (i + 1) * 100.3f;
+            final float gain = (i+1) * 10.1f;
+            EqBand eqBand = new EqBand(eq.getBand(i));
+
+            eqBand.setEnabled(enabled);
+            eqBand.setCutoffFrequency(frequency);
+            eqBand.setGain(gain);
+
+            eq.setBand(i, eqBand);
+
+            //compare
+            assertEquals("eq enabled is different in band " + i, enabled,
+                    eq.getBand(i).isEnabled());
+            assertEquals("eq cutoffFrequency is different in band "+ i,
+                    frequency, eq.getBand(i).getCutoffFrequency(), EPSILON);
+            assertEquals("eq eqBand gain is different in band "+ i,
+                    gain, eq.getBand(i).getGain(), EPSILON);
+        }
+    }
+
+    public void test2_7Mbc() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int bandCount = 3;
+
+        Mbc mbc = new Mbc(inUse, enabled, bandCount);
+        assertEquals("mbc inUse is different", inUse, mbc.isInUse());
+        assertEquals("mbc enabled is different", enabled, mbc.isEnabled());
+        assertEquals("mbc bandCount is different", bandCount, mbc.getBandCount());
+
+        //changes
+        mbc.setEnabled(!enabled);
+        assertEquals("enabled is different", !enabled, mbc.isEnabled());
+
+        //bands
+        for (int i = 0; i < bandCount; i++) {
+            int index = i+1;
+            final float frequency = index * 100.3f;
+            final float attackTime = index * 3.2f;
+            final float releaseTime = 2 * attackTime;
+            final float ratio = index * 1.2f;
+            final float threshold = index * (-12.8f);
+            final float kneeWidth = index * 0.3f;
+            final float noiseGateThreshold = index * (-20.1f);
+            final float expanderRatio = index * 1.1f;
+            final float preGain = index * 10.1f;
+            final float postGain = index * (-0.2f);
+            MbcBand mbcBand = new MbcBand(mbc.getBand(i));
+
+            mbcBand.setEnabled(enabled);
+            mbcBand.setCutoffFrequency(frequency);
+            mbcBand.setAttackTime(attackTime);
+            mbcBand.setReleaseTime(releaseTime);
+            mbcBand.setRatio(ratio);
+            mbcBand.setThreshold(threshold);
+            mbcBand.setKneeWidth(kneeWidth);
+            mbcBand.setNoiseGateThreshold(noiseGateThreshold);
+            mbcBand.setExpanderRatio(expanderRatio);
+            mbcBand.setPreGain(preGain);
+            mbcBand.setPostGain(postGain);
+
+            mbc.setBand(i, mbcBand);
+
+            //compare
+            assertEquals("mbc enabled is different", enabled, mbc.getBand(i).isEnabled());
+            assertEquals("mbc cutoffFrequency is different in band "+ i,
+                    frequency, mbc.getBand(i).getCutoffFrequency(), EPSILON);
+            assertEquals("mbc attackTime is different in band "+ i,
+                    attackTime, mbc.getBand(i).getAttackTime(), EPSILON);
+            assertEquals("mbc releaseTime is different in band "+ i,
+                    releaseTime, mbc.getBand(i).getReleaseTime(), EPSILON);
+            assertEquals("mbc ratio is different in band "+ i,
+                    ratio, mbc.getBand(i).getRatio(), EPSILON);
+            assertEquals("mbc threshold is different in band "+ i,
+                    threshold, mbc.getBand(i).getThreshold(), EPSILON);
+            assertEquals("mbc kneeWidth is different in band "+ i,
+                    kneeWidth, mbc.getBand(i).getKneeWidth(), EPSILON);
+            assertEquals("mbc noiseGateThreshold is different in band "+ i,
+                    noiseGateThreshold, mbc.getBand(i).getNoiseGateThreshold(), EPSILON);
+            assertEquals("mbc expanderRatio is different in band "+ i,
+                    expanderRatio, mbc.getBand(i).getExpanderRatio(), EPSILON);
+            assertEquals("mbc preGain is different in band "+ i,
+                    preGain, mbc.getBand(i).getPreGain(), EPSILON);
+            assertEquals("mbc postGain is different in band "+ i,
+                    postGain, mbc.getBand(i).getPostGain(), EPSILON);
+        }
+    }
+
+    public void test2_8Limiter() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int linkGroup = 4;
+        final float attackTime = 3.2f;
+        final float releaseTime = 2 * attackTime;
+        final float ratio = 1.2f;
+        final float threshold = (-12.8f);
+        final float postGain = (-0.2f);
+
+        Limiter limiter = new Limiter(inUse, enabled, linkGroup, attackTime, releaseTime, ratio,
+                threshold, postGain);
+        assertEquals("limiter inUse is different", inUse, limiter.isInUse());
+        assertEquals("limiter enabled is different", enabled, limiter.isEnabled());
+        assertEquals("limiter linkGroup is different", linkGroup, limiter.getLinkGroup());
+
+        //defaults
+        assertEquals("limiter attackTime is different",
+                attackTime, limiter.getAttackTime(), EPSILON);
+        assertEquals("limiter releaseTime is different",
+                releaseTime, limiter.getReleaseTime(), EPSILON);
+        assertEquals("limiter ratio is different",
+                ratio, limiter.getRatio(), EPSILON);
+        assertEquals("limiter threshold is different",
+                threshold, limiter.getThreshold(), EPSILON);
+        assertEquals("limiter postGain is different",
+                postGain, limiter.getPostGain(), EPSILON);
+
+        //changes
+        final boolean newEnabled = !enabled;
+        final int newLinkGroup = 7;
+        final float newAttackTime = attackTime + 10;
+        final float newReleaseTime = releaseTime + 10;
+        final float newRatio = ratio + 2f;
+        final float newThreshold = threshold -20f;
+        final float newPostGain = postGain + 3f;
+
+        limiter.setEnabled(newEnabled);
+        limiter.setLinkGroup(newLinkGroup);
+        limiter.setAttackTime(newAttackTime);
+        limiter.setReleaseTime(newReleaseTime);
+        limiter.setRatio(newRatio);
+        limiter.setThreshold(newThreshold);
+        limiter.setPostGain(newPostGain);
+
+        assertEquals("limiter enabled is different", newEnabled, limiter.isEnabled());
+        assertEquals("limiter linkGroup is different", newLinkGroup, limiter.getLinkGroup());
+        assertEquals("limiter attackTime is different",
+                newAttackTime, limiter.getAttackTime(), EPSILON);
+        assertEquals("limiter releaseTime is different",
+                newReleaseTime, limiter.getReleaseTime(), EPSILON);
+        assertEquals("limiter ratio is different",
+                newRatio, limiter.getRatio(), EPSILON);
+        assertEquals("limiter threshold is different",
+                newThreshold, limiter.getThreshold(), EPSILON);
+        assertEquals("limiter postGain is different",
+                newPostGain, limiter.getPostGain(), EPSILON);
+    }
+
+    public void test2_9BandStage() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int bandCount = 3;
+
+        BandStage bandStage = new BandStage(inUse, enabled, bandCount);
+        assertEquals("bandStage inUse is different", inUse, bandStage.isInUse());
+        assertEquals("bandStage enabled is different", enabled, bandStage.isEnabled());
+        assertEquals("bandStage bandCount is different", bandCount, bandStage.getBandCount());
+
+        //change
+        bandStage.setEnabled(!enabled);
+        assertEquals("bandStage enabled is different", !enabled, bandStage.isEnabled());
+    }
+
+    public void test2_10Stage() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int bandCount = 3;
+
+        DynamicsProcessing.Stage stage = new DynamicsProcessing.Stage(inUse, enabled);
+        assertEquals("stage inUse is different", inUse, stage.isInUse());
+        assertEquals("stage enabled is different", enabled, stage.isEnabled());
+
+        //change
+        stage.setEnabled(!enabled);
+        assertEquals("stage enabled is different", !enabled, stage.isEnabled());
+    }
+
+    public void test2_11BandBase() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean enabled = true;
+        final float frequency = 100.3f;
+
+        BandBase bandBase = new BandBase(enabled, frequency);
+
+        assertEquals("bandBase enabled is different", enabled, bandBase.isEnabled());
+        assertEquals("bandBase cutoffFrequency is different",
+                frequency, bandBase.getCutoffFrequency(), EPSILON);
+
+        //change
+        final float newFrequency = frequency + 10f;
+        bandBase.setEnabled(!enabled);
+        bandBase.setCutoffFrequency(newFrequency);
+        assertEquals("bandBase enabled is different", !enabled, bandBase.isEnabled());
+        assertEquals("bandBase cutoffFrequency is different",
+                newFrequency, bandBase.getCutoffFrequency(), EPSILON);
+    }
+    //-----------------------------------------------------------------
+    // 3 - Builder
+    //----------------------------------
+
+    public void test3_0Builder_stagesAllChannels() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+        //get Stages, apply all channels
+        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+        preEqBand.setGain(TEST_GAIN1);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        builder.setPreEqAllChannelsTo(preEq);
+
+        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+        mbcBand.setPreGain(TEST_GAIN1);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        builder.setMbcAllChannelsTo(mbc);
+
+        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+        postEqBand.setGain(TEST_GAIN1);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        builder.setPostEqAllChannelsTo(postEq);
+
+        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+        limiter.setPostGain(TEST_GAIN1);
+        builder.setLimiterAllChannelsTo(limiter);
+
+        //build and compare
+        DynamicsProcessing.Config newConfig = builder.build();
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN1, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
+
+    public void test3_1Builder_stagesByChannelIndex() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+
+        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+
+        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+
+        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+
+        //get Stages, apply per channel
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+
+            preEqBand.setGain(gain);
+            preEq.setBand(TEST_BAND_INDEX, preEqBand);
+            builder.setPreEqByChannelIndex(i, preEq);
+
+            mbcBand.setPreGain(gain);
+            mbc.setBand(TEST_BAND_INDEX, mbcBand);
+            builder.setMbcByChannelIndex(i, mbc);
+
+            postEqBand.setGain(gain);
+            postEq.setBand(TEST_BAND_INDEX, postEqBand);
+            builder.setPostEqByChannelIndex(i,postEq);
+
+            limiter.setPostGain(gain);
+            builder.setLimiterByChannelIndex(i, limiter);
+        }
+        //build and compare
+        DynamicsProcessing.Config newConfig = builder.build();
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    expectedGain,
+                    newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    expectedGain,
+                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    expectedGain,
+                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("limiter gain is different in channel " + i,
+                    expectedGain, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
+
+    public void test3_2Builder_setAllChannelsTo() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+        //get Stages, apply all channels
+        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+        preEqBand.setGain(TEST_GAIN1);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        channel.setPreEq(preEq);
+
+        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+        mbcBand.setPreGain(TEST_GAIN1);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        channel.setMbc(mbc);
+
+        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+        postEqBand.setGain(TEST_GAIN1);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        channel.setPostEq(postEq);
+
+        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+        limiter.setPostGain(TEST_GAIN1);
+        channel.setLimiter(limiter);
+
+        builder.setAllChannelsTo(channel);
+        //build and compare
+        DynamicsProcessing.Config newConfig = builder.build();
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    TEST_GAIN1, newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN1, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
+
+    public void test3_3Builder_setChannelTo() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+
+        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+
+        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+
+        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+
+        //get Stages, apply per channel
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+
+            preEqBand.setGain(gain);
+            preEq.setBand(TEST_BAND_INDEX, preEqBand);
+            channel.setPreEq(preEq);
+
+            mbcBand.setPreGain(gain);
+            mbc.setBand(TEST_BAND_INDEX, mbcBand);
+            channel.setMbc(mbc);
+
+            postEqBand.setGain(gain);
+            postEq.setBand(TEST_BAND_INDEX, postEqBand);
+            channel.setPostEq(postEq);
+
+            limiter.setPostGain(gain);
+            channel.setLimiter(limiter);
+
+            builder.setChannelTo(i, channel);
+        }
+        //build and compare
+        DynamicsProcessing.Config newConfig = builder.build();
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+            assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    expectedGain,
+                    newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    expectedGain,
+                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+                    expectedGain,
+                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("limiter gain is different in channel " + i,
+                    expectedGain, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
     //-----------------------------------------------------------------
     // private methods
     //----------------------------------
@@ -260,11 +1099,11 @@
         createDynamicsProcessingWithConfig(session, config);
     }
 
-    private DynamicsProcessing.Config.Builder getBuilder() {
+    private DynamicsProcessing.Config.Builder getBuilder(int channelCount) {
         //simple config
         DynamicsProcessing.Config.Builder builder = new DynamicsProcessing.Config.Builder(
                 DEFAULT_VARIANT /* variant */,
-                MIN_CHANNEL_COUNT /* channels */,
+                channelCount/* channels */,
                 DEFAULT_PREEQ_IN_USE /*enable preEQ*/,
                 DEFAULT_PREEQ_BAND_COUNT /*preEq bands*/,
                 DEFAULT_MBC_IN_USE /*enable mbc*/,
@@ -276,13 +1115,18 @@
         return builder;
     }
 
-    private DynamicsProcessing.Config.Builder getBuilderWithValues() {
+    private DynamicsProcessing.Config.Builder getBuilderWithValues(int channelCount) {
         //simple config
-        DynamicsProcessing.Config.Builder builder = getBuilder();
+        DynamicsProcessing.Config.Builder builder = getBuilder(channelCount);
 
         //Set Defaults
         builder.setPreferredFrameDuration(DEFAULT_FRAME_DURATION);
         builder.setInputGainAllChannelsTo(DEFAULT_INPUT_GAIN);
         return builder;
     }
+
+    private DynamicsProcessing.Config.Builder getBuilderWithValues() {
+        return getBuilderWithValues(MIN_CHANNEL_COUNT);
+    }
+
 }
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
new file mode 100644
index 0000000..08a6527
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
@@ -0,0 +1,569 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.ImageFormat;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.opengl.GLES20;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_BITMAP;
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_BUFFER;
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_SURFACE;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.heifwriter.HeifWriter;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+public class HeifWriterTest extends AndroidTestCase {
+    private static final String TAG = HeifWriterTest.class.getSimpleName();
+    private static final boolean DEBUG = false;
+    private static final boolean DUMP_YUV_INPUT = false;
+
+    private static byte[][] TEST_COLORS = {
+            {(byte) 255, (byte) 0, (byte) 0},
+            {(byte) 255, (byte) 0, (byte) 255},
+            {(byte) 255, (byte) 255, (byte) 255},
+            {(byte) 255, (byte) 255, (byte) 0},
+    };
+
+    private static final String HEIFWRITER_INPUT = "heifwriter_input.heic";
+    private static final int[] IMAGE_RESOURCES = new int[] {
+            R.raw.heifwriter_input
+    };
+    private static final String[] IMAGE_FILENAMES = new String[] {
+            HEIFWRITER_INPUT
+    };
+    private static final String OUTPUT_FILENAME = "output.heic";
+
+    private InputSurface mInputEglSurface;
+    private Handler mHandler;
+    private int mInputIndex;
+
+    @Override
+    public void setUp() throws Exception {
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String outputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+
+            InputStream inputStream = null;
+            FileOutputStream outputStream = null;
+            try {
+                inputStream = getContext().getResources().openRawResource(IMAGE_RESOURCES[i]);
+                outputStream = new FileOutputStream(outputPath);
+                copy(inputStream, outputStream);
+            } finally {
+                closeQuietly(inputStream);
+                closeQuietly(outputStream);
+            }
+        }
+
+        HandlerThread handlerThread = new HandlerThread(
+                "HeifEncoderThread", Process.THREAD_PRIORITY_FOREGROUND);
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String imageFilePath =
+                    new File(Environment.getExternalStorageDirectory(), IMAGE_FILENAMES[i])
+                            .getAbsolutePath();
+            File imageFile = new File(imageFilePath);
+            if (imageFile.exists()) {
+                imageFile.delete();
+            }
+        }
+    }
+
+    public void testInputBuffer_NoGrid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, false);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputBuffer_Grid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, false);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputBuffer_NoGrid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, true);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputBuffer_Grid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, true);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputSurface_NoGrid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, false);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputSurface_Grid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, false);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputSurface_NoGrid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, true);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputSurface_Grid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, true);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputBitmap_NoGrid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, false);
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String inputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+            doTestForVariousNumberImages(builder.setInputPath(inputPath));
+        }
+    }
+
+    public void testInputBitmap_Grid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, false);
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String inputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+            doTestForVariousNumberImages(builder.setInputPath(inputPath));
+        }
+    }
+
+    public void testInputBitmap_NoGrid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, true);
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String inputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+            doTestForVariousNumberImages(builder.setInputPath(inputPath));
+        }
+    }
+
+    public void testInputBitmap_Grid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, true);
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String inputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+            doTestForVariousNumberImages(builder.setInputPath(inputPath));
+        }
+    }
+
+    private static boolean canEncodeHeic() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_HEVC)
+            || MediaUtils.hasEncoder(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC);
+    }
+
+    private void doTestForVariousNumberImages(TestConfig.Builder builder) throws Exception {
+        if (!canEncodeHeic()) {
+            MediaUtils.skipTest("heic encoding is not supported on this device");
+            return;
+        }
+
+        builder.setNumImages(4);
+        doTest(builder.setRotation(270).build());
+        doTest(builder.setRotation(180).build());
+        doTest(builder.setRotation(90).build());
+        doTest(builder.setRotation(0).build());
+        doTest(builder.setNumImages(1).build());
+        doTest(builder.setNumImages(8).build());
+    }
+
+    private void closeQuietly(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    private int copy(InputStream in, OutputStream out) throws IOException {
+        int total = 0;
+        byte[] buffer = new byte[8192];
+        int c;
+        while ((c = in.read(buffer)) != -1) {
+            total += c;
+            out.write(buffer, 0, c);
+        }
+        return total;
+    }
+
+    private static class TestConfig {
+        final int mInputMode;
+        final boolean mUseGrid;
+        final boolean mUseHandler;
+        final int mMaxNumImages;
+        final int mNumImages;
+        final int mWidth;
+        final int mHeight;
+        final int mRotation;
+        final int mQuality;
+        final String mInputPath;
+        final String mOutputPath;
+        final Bitmap[] mBitmaps;
+
+        TestConfig(int inputMode, boolean useGrid, boolean useHandler,
+                   int maxNumImages, int numImages, int width, int height,
+                   int rotation, int quality,
+                   String inputPath, String outputPath, Bitmap[] bitmaps) {
+            mInputMode = inputMode;
+            mUseGrid = useGrid;
+            mUseHandler = useHandler;
+            mMaxNumImages = maxNumImages;
+            mNumImages = numImages;
+            mWidth = width;
+            mHeight = height;
+            mRotation = rotation;
+            mQuality = quality;
+            mInputPath = inputPath;
+            mOutputPath = outputPath;
+            mBitmaps = bitmaps;
+        }
+
+        static class Builder {
+            final int mInputMode;
+            final boolean mUseGrid;
+            final boolean mUseHandler;
+            int mMaxNumImages;
+            int mNumImages;
+            int mWidth;
+            int mHeight;
+            int mRotation;
+            final int mQuality;
+            String mInputPath;
+            final String mOutputPath;
+            Bitmap[] mBitmaps;
+            boolean mNumImagesSetExplicitly;
+
+
+            Builder(int inputMode, boolean useGrids, boolean useHandler) {
+                mInputMode = inputMode;
+                mUseGrid = useGrids;
+                mUseHandler = useHandler;
+                mMaxNumImages = mNumImages = 4;
+                mWidth = 1920;
+                mHeight = 1080;
+                mRotation = 0;
+                mQuality = 100;
+                mOutputPath = new File(Environment.getExternalStorageDirectory(),
+                        OUTPUT_FILENAME).getAbsolutePath();
+            }
+
+            Builder setInputPath(String inputPath) {
+                mInputPath = (mInputMode == INPUT_MODE_BITMAP) ? inputPath : null;
+                return this;
+            }
+
+            Builder setNumImages(int numImages) {
+                mNumImagesSetExplicitly = true;
+                mNumImages = numImages;
+                return this;
+            }
+
+            Builder setRotation(int rotation) {
+                mRotation = rotation;
+                return this;
+            }
+
+            private void loadBitmapInputs() {
+                if (mInputMode != INPUT_MODE_BITMAP) {
+                    return;
+                }
+                MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+                retriever.setDataSource(mInputPath);
+                String hasImage = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
+                if (!"yes".equals(hasImage)) {
+                    throw new IllegalArgumentException("no bitmap found!");
+                }
+                mMaxNumImages = Math.min(mMaxNumImages, Integer.parseInt(retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
+                if (!mNumImagesSetExplicitly) {
+                    mNumImages = mMaxNumImages;
+                }
+                mBitmaps = new Bitmap[mMaxNumImages];
+                for (int i = 0; i < mBitmaps.length; i++) {
+                    mBitmaps[i] = retriever.getImageAtIndex(i);
+                }
+                mWidth = mBitmaps[0].getWidth();
+                mHeight = mBitmaps[0].getHeight();
+                retriever.release();
+            }
+
+            private void cleanupStaleOutputs() {
+                File outputFile = new File(mOutputPath);
+                if (outputFile.exists()) {
+                    outputFile.delete();
+                }
+            }
+
+            TestConfig build() {
+                cleanupStaleOutputs();
+                loadBitmapInputs();
+
+                return new TestConfig(mInputMode, mUseGrid, mUseHandler, mMaxNumImages, mNumImages,
+                        mWidth, mHeight, mRotation, mQuality, mInputPath, mOutputPath, mBitmaps);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "TestConfig"
+                    + ": mInputMode " + mInputMode
+                    + ", mUseGrid " + mUseGrid
+                    + ", mUseHandler " + mUseHandler
+                    + ", mMaxNumImages " + mMaxNumImages
+                    + ", mNumImages " + mNumImages
+                    + ", mWidth " + mWidth
+                    + ", mHeight " + mHeight
+                    + ", mRotation " + mRotation
+                    + ", mQuality " + mQuality
+                    + ", mInputPath " + mInputPath
+                    + ", mOutputPath " + mOutputPath;
+        }
+    }
+
+    private void doTest(TestConfig config) throws Exception {
+        int width = config.mWidth;
+        int height = config.mHeight;
+        int numImages = config.mNumImages;
+
+        mInputIndex = 0;
+        HeifWriter heifWriter = null;
+        FileInputStream inputStream = null;
+        FileOutputStream outputStream = null;
+        try {
+            if (DEBUG) Log.d(TAG, "started: " + config);
+
+            heifWriter = new HeifWriter.Builder(
+                    config.mOutputPath, width, height, config.mInputMode)
+                    .setRotation(config.mRotation)
+                    .setGridEnabled(config.mUseGrid)
+                    .setMaxImages(config.mMaxNumImages)
+                    .setQuality(config.mQuality)
+                    .setPrimaryIndex(config.mMaxNumImages - 1)
+                    .setHandler(config.mUseHandler ? mHandler : null)
+                    .build();
+
+            if (config.mInputMode == INPUT_MODE_SURFACE) {
+                mInputEglSurface = new InputSurface(heifWriter.getInputSurface());
+            }
+
+            heifWriter.start();
+
+            if (config.mInputMode == INPUT_MODE_BUFFER) {
+                byte[] data = new byte[width * height * 3 / 2];
+
+                if (config.mInputPath != null) {
+                    inputStream = new FileInputStream(config.mInputPath);
+                }
+
+                if (DUMP_YUV_INPUT) {
+                    File outputFile = new File("/sdcard/input.yuv");
+                    outputFile.createNewFile();
+                    outputStream = new FileOutputStream(outputFile);
+                }
+
+                for (int i = 0; i < numImages; i++) {
+                    if (DEBUG) Log.d(TAG, "fillYuvBuffer: " + i);
+                    fillYuvBuffer(i, data, width, height, inputStream);
+                    if (DUMP_YUV_INPUT) {
+                        Log.d(TAG, "@@@ dumping input YUV");
+                        outputStream.write(data);
+                    }
+                    heifWriter.addYuvBuffer(ImageFormat.YUV_420_888, data);
+                }
+            } else if (config.mInputMode == INPUT_MODE_SURFACE) {
+                // The input surface is a surface texture using single buffer mode, draws will be
+                // blocked until onFrameAvailable is done with the buffer, which is dependant on
+                // how fast MediaCodec processes them, which is further dependent on how fast the
+                // MediaCodec callbacks are handled. We can't put draws on the same looper that
+                // handles MediaCodec callback, it will cause deadlock.
+                for (int i = 0; i < numImages; i++) {
+                    if (DEBUG) Log.d(TAG, "drawFrame: " + i);
+                    drawFrame(width, height);
+                }
+                heifWriter.setInputEndOfStreamTimestamp(
+                        1000 * computePresentationTime(numImages - 1));
+            } else if (config.mInputMode == INPUT_MODE_BITMAP) {
+                Bitmap[] bitmaps = config.mBitmaps;
+                for (int i = 0; i < Math.min(bitmaps.length, numImages); i++) {
+                    if (DEBUG) Log.d(TAG, "addBitmap: " + i);
+                    heifWriter.addBitmap(bitmaps[i]);
+                    bitmaps[i].recycle();
+                }
+            }
+
+            heifWriter.stop(3000);
+            verifyResult(config.mOutputPath, width, height, config.mRotation, config.mUseGrid,
+                    Math.min(numImages, config.mMaxNumImages));
+            if (DEBUG) Log.d(TAG, "finished: PASS");
+        } finally {
+            try {
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+            } catch (IOException e) {}
+
+            if (heifWriter != null) {
+                heifWriter.close();
+                heifWriter = null;
+            }
+            if (mInputEglSurface != null) {
+                // This also releases the surface from encoder.
+                mInputEglSurface.release();
+                mInputEglSurface = null;
+            }
+        }
+    }
+
+    private long computePresentationTime(int frameIndex) {
+        return 132 + (long)frameIndex * 1000000;
+    }
+
+    private void fillYuvBuffer(int frameIndex, @NonNull byte[] data, int width, int height,
+                               @Nullable FileInputStream inputStream) throws IOException {
+        if (inputStream != null) {
+            inputStream.read(data);
+        } else {
+            byte[] color = TEST_COLORS[frameIndex % TEST_COLORS.length];
+            int sizeY = width * height;
+            Arrays.fill(data, 0, sizeY, color[0]);
+            Arrays.fill(data, sizeY, sizeY * 5 / 4, color[1]);
+            Arrays.fill(data, sizeY * 5 / 4, sizeY * 3 / 2, color[2]);
+        }
+    }
+
+    private void drawFrame(int width, int height) {
+        mInputEglSurface.makeCurrent();
+        generateSurfaceFrame(mInputIndex, width, height);
+        mInputEglSurface.setPresentationTime(1000 * computePresentationTime(mInputIndex));
+        mInputEglSurface.swapBuffers();
+        mInputIndex++;
+    }
+
+    private void generateSurfaceFrame(int frameIndex, int width, int height) {
+        frameIndex %= 4;
+
+        GLES20.glViewport(0, 0, width, height);
+        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+
+        int startX, startY;
+        int borderWidth = 16;
+        for (int i = 0; i < 7; i++) {
+            startX = (width - borderWidth * 2) * i / 7 + borderWidth;
+            GLES20.glScissor(startX, borderWidth,
+                    (width - borderWidth * 2) / 7, height - borderWidth * 2);
+            GLES20.glClearColor(((7 - i) & 0x4) * 0.16f,
+                    ((7 - i) & 0x2) * 0.32f,
+                    ((7 - i) & 0x1) * 0.64f,
+                    1.0f);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        }
+
+        startX = (width / 6) + (width / 6) * frameIndex;
+        startY = height / 4;
+        GLES20.glScissor(startX, startY, width / 6, height / 3);
+        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glScissor(startX + borderWidth, startY + borderWidth,
+                width / 6 - borderWidth * 2, height / 3 - borderWidth * 2);
+        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    }
+
+    private void verifyResult(
+            String filename, int width, int height, int rotation, boolean useGrid, int numImages)
+            throws Exception {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(filename);
+        String hasImage = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
+        if (!"yes".equals(hasImage)) {
+            throw new Exception("No images found in file " + filename);
+        }
+        assertEquals("Wrong image count", numImages,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
+        assertEquals("Wrong width", width,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)));
+        assertEquals("Wrong height", height,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)));
+        assertEquals("Wrong rotation", rotation,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)));
+        retriever.release();
+
+        if (useGrid) {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(filename);
+            MediaFormat format = extractor.getTrackFormat(0);
+            int tileWidth = format.getInteger(MediaFormat.KEY_TILE_WIDTH);
+            int tileHeight = format.getInteger(MediaFormat.KEY_TILE_HEIGHT);
+            int gridRows = format.getInteger(MediaFormat.KEY_GRID_ROWS);
+            int gridCols = format.getInteger(MediaFormat.KEY_GRID_COLUMNS);
+            assertTrue("Wrong tile width or grid cols",
+                    ((width + tileWidth - 1) / tileWidth) == gridCols);
+            assertTrue("Wrong tile height or grid rows",
+                    ((height + tileHeight - 1) / tileHeight) == gridRows);
+            extractor.release();
+        }
+    }
+}
diff --git a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
index 1e6f05e..31d6848 100644
--- a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
+++ b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
@@ -39,6 +39,13 @@
 namespace android {
 namespace {
 
+// The 'stride' field is ignored by AHardwareBuffer_allocate, so we can use it
+// to pass these flags.
+enum TestFlags {
+    kGlFormat = 0x1,  // The 'format' field specifies a GL format.
+    kUseSrgb = 0x2,  // Whether to use the sRGB transfer function.
+};
+
 #define FORMAT_CASE(x) case x: return #x; break
 const char* AHBFormatAsString(int32_t format) {
     switch (format) {
@@ -131,6 +138,7 @@
 
 // Uploads opaque red to the currently bound texture.
 void UploadRedPixels(const AHardwareBuffer_Desc& desc) {
+    const bool use_srgb = desc.stride & kUseSrgb;
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
     switch (desc.format) {
         case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
@@ -141,7 +149,7 @@
             const int size = desc.width * desc.height * 3;
             std::unique_ptr<uint8_t[]> pixels(new uint8_t[size]);
             for (int i = 0; i < size; i += 3) {
-                pixels[i] = 255;
+                pixels[i] = use_srgb ? 188 : 255;
                 pixels[i + 1] = 0;
                 pixels[i + 2] = 0;
             }
@@ -154,10 +162,10 @@
             const int size = desc.width * desc.height * 4;
             std::unique_ptr<uint8_t[]> pixels(new uint8_t[size]);
             for (int i = 0; i < size; i += 4) {
-                pixels[i] = 255;
+                pixels[i] = use_srgb ? 188 : 255;
                 pixels[i + 1] = 0;
                 pixels[i + 2] = 0;
-                pixels[i + 3] = 255;
+                pixels[i + 3] = use_srgb ? 128 : 255;
             }
             UploadData(desc, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get());
             break;
@@ -193,28 +201,35 @@
 
 // Draws the following checkerboard pattern using glScissor and glClear.
 // The number after the color is the stencil value and the floating point number is the depth value.
+// The pattern is asymmetric to detect coordinate system mixups.
 //        +-----+-----+ (W, H)
-//        | OR1 | Ob2 |
+//        | OR1 | OB2 |
 //        | 0.5 | 0.0 |
-//        +-----+-----+  TB = transparent black
-//        | TB0 | OR1 |  OR = opaque red
-//        | 1.0 | 0.5 |  Ob = opaque blue
-// (0, 0) +-----+-----+
+//        +-----+-----+  Tb = transparent black
+//        | Tb0 | OG3 |  OR = opaque red
+//        | 1.0 | 1.0 |  OG = opaque green
+// (0, 0) +-----+-----+  OB = opaque blue
 //
 void DrawCheckerboard(int width, int height) {
     glEnable(GL_SCISSOR_TEST);
     const GLbitfield all_bits = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
 
-    glClearColor(1.f, 0.f, 0.f, 1.f);
-    glClearDepthf(0.5f);
-    glClearStencil(1);
-    glScissor(0, 0, width, height);
-    glClear(all_bits);
-
     glClearColor(0.f, 0.f, 0.f, 0.f);
     glClearDepthf(1.0f);
     glClearStencil(0);
-    glScissor(0, 0, width / 2, height / 2);
+    glScissor(0, 0, width, height);
+    glClear(all_bits);
+
+    glClearColor(1.f, 0.f, 0.f, 1.f);
+    glClearDepthf(0.5f);
+    glClearStencil(1);
+    glScissor(0, height / 2, width / 2, height / 2);
+    glClear(all_bits);
+
+    glClearColor(0.f, 1.f, 0.f, 1.f);
+    glClearDepthf(1.0f);
+    glClearStencil(3);
+    glScissor(width / 2, 0, width / 2, height / 2);
     glClear(all_bits);
 
     glClearColor(0.f, 0.f, 1.f, 1.f);
@@ -231,9 +246,11 @@
     kZero,  // all zero, i.e., transparent black
     kBlack,  // opaque black
     kRed,  // opaque red
+    kGreen,  // opaque green
     kBlue,  // opaque blue
     kRed50,  // 50% premultiplied red, i.e., (0.5, 0, 0, 0.5)
     kRed50Srgb,  // 50% premultiplied red under sRGB transfer function
+    kRed50Alpha100,  // Opaque 50% red
 };
 
 struct GoldenPixel {
@@ -274,6 +291,7 @@
     switch (golden.color) {
         case kRed: golden_pixel[0] = 255; break;
         case kRed50:
+        case kRed50Alpha100:
             use_range = true;
             golden_pixel[0] = 127;
             golden_max[0] = 128;
@@ -283,6 +301,7 @@
             golden_pixel[0] = 187;
             golden_max[0] = 188;
             break;
+        case kGreen: golden_pixel[1] = 255; break;
         case kBlue: golden_pixel[2] = 255; break;
         case kZero: if (FormatHasAlpha(format)) golden_pixel[3] = 0; break;
         case kBlack: break;
@@ -311,6 +330,7 @@
     switch (golden.color) {
         case kRed: golden_pixel[0] = 1.f; break;
         case kRed50: golden_pixel[0] = 0.5f; golden_pixel[3] = 0.5f; break;
+        case kGreen: golden_pixel[1] = 1.f; break;
         case kBlue: golden_pixel[2] = 1.f; break;
         case kZero: golden_pixel[3] = 0.f; break;
         case kBlack: break;
@@ -528,7 +548,7 @@
     void main() {
         uvec4 stencil = texture(uTexture, vTexCoords);
         color.r = stencil.x == 1u ? 1.0 : 0.0;
-        color.g = 0.0;
+        color.g = stencil.x == 3u ? 1.0 : 0.0;
         color.b = stencil.x == 2u ? 1.0 : 0.0;
         color.a = stencil.x == 0u ? 0.0 : 1.0;
     }
@@ -543,7 +563,7 @@
     void main() {
         uvec4 stencil = texture(uTexture, vec3(vTexCoords, uLayer));
         color.r = stencil.x == 1u ? 1.0 : 0.0;
-        color.g = 0.0;
+        color.g = stencil.x == 3u ? 1.0 : 0.0;
         color.b = stencil.x == 2u ? 1.0 : 0.0;
         color.a = stencil.x == 0u ? 0.0 : 1.0;
     }
@@ -586,7 +606,7 @@
     };
 
     void SetUp() override;
-    virtual bool SetUpBuffer(const AHardwareBuffer_Desc& desc, bool use_srgb = false);
+    virtual bool SetUpBuffer(const AHardwareBuffer_Desc& desc);
     void SetUpProgram(const std::string& vertex_source, const std::string& fragment_source,
                       const float* mesh, float scale, int texture_unit = 0);
     void SetUpTexture(const AHardwareBuffer_Desc& desc, int unit);
@@ -597,7 +617,7 @@
     void TearDown() override;
 
     void MakeCurrent(int which) {
-        if (GetParam().stride != 0) return;
+        if (GetParam().stride & kGlFormat) return;
         mWhich = which;
         eglMakeCurrent(mDisplay, mSurface, mSurface, mContext[mWhich]);
     }
@@ -607,6 +627,17 @@
     bool HasGLExtension(const std::string& s) {
         return mGLExtensions.find(s) != mGLExtensions.end();
     }
+    bool IsFormatColorRenderable(uint32_t format, bool use_srgb) {
+        if (use_srgb) {
+            // According to the spec, GL_SRGB8 is not color-renderable.
+            return format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM || format == GL_SRGB8_ALPHA8;
+        } else {
+            if (format == GL_RGBA16F || format == AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT) {
+                return mGLVersion >= 32 || HasGLExtension("GL_EXT_color_buffer_float");
+            }
+            return true;
+        }
+    }
 
 protected:
     std::set<std::string> mEGLExtensions;
@@ -622,6 +653,7 @@
     EGLImageKHR mEGLImage = EGL_NO_IMAGE_KHR;
     GLenum mTexTarget = GL_NONE;
     GLuint mProgram = 0;
+    GLint mColorLocation = -1;
     GLint mFaceVectorLocation = -1;
     GLuint mTextures[2] = { 0, 0 };
     GLuint mBufferObjects[2] = { 0, 0 };
@@ -694,7 +726,8 @@
     ASSERT_GE(mGLVersion, 20);
 }
 
-bool AHardwareBufferGLTest::SetUpBuffer(const AHardwareBuffer_Desc& desc, bool use_srgb) {
+bool AHardwareBufferGLTest::SetUpBuffer(const AHardwareBuffer_Desc& desc) {
+    const bool use_srgb = desc.stride & kUseSrgb;
     if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_CUBE_MAP) {
         if (desc.layers > 6) {
             if (mGLVersion < 32) {
@@ -728,16 +761,16 @@
               mGLVersion / 10, mGLVersion % 10);
         return false;
     }
-    if (desc.format == GL_RGBA16F &&
-        (!HasGLExtension("GL_EXT_color_buffer_float") && mGLVersion < 32)) {
-        ALOGI("Test skipped: GL_RGBA16F requires GL_EXT_color_buffer_float or GL ES 3.2");
+    if (desc.format == GL_RGBA16F && mGLVersion < 30) {
+        ALOGI("Test skipped: GL_RGBA16F requires GL ES 3.0, found %d.%d",
+              mGLVersion / 10, mGLVersion % 10);
         return false;
     }
-    // Nonzero stride indicates that desc.format should be interpreted as a GL format
-    // and the test should be run in a single context, without using AHardwareBuffer.
-    // This simplifies verifying that the test behaves as expected even if the
-    // AHardwareBuffer format under test is not supported.
-    if (desc.stride != 0) {
+    // For control cases using GL formats, the test should be run in a single
+    // context, without using AHardwareBuffer. This simplifies verifying that
+    // the test behaves as expected even if the AHardwareBuffer format under
+    // test is not supported.
+    if (desc.stride & kGlFormat) {
         mContextCount = 1;
         return true;
     }
@@ -821,9 +854,9 @@
         FAIL() << "Unknown mesh";
     }
     glUniform1f(glGetUniformLocation(mProgram, "uScale"), scale);
-    GLint u_color_location = glGetUniformLocation(mProgram, "uColor");
-    if (u_color_location >= 0) {
-        glUniform4f(u_color_location, 1.f, 0.f, 0.f, 1.f);
+    mColorLocation = glGetUniformLocation(mProgram, "uColor");
+    if (mColorLocation >= 0) {
+        glUniform4f(mColorLocation, 1.f, 0.f, 0.f, 1.f);
     }
     GLint u_texture_location = glGetUniformLocation(mProgram, "uTexture");
     if (u_texture_location >= 0) {
@@ -841,14 +874,7 @@
     glGenTextures(1, &texture);
     glActiveTexture(GL_TEXTURE0 + unit);
     glBindTexture(mTexTarget, texture);
-    if (desc.stride == 0) {
-        if (HasGLExtension("GL_EXT_EGL_image_storage")) {
-            glEGLImageTargetTexStorageEXT(mTexTarget, static_cast<GLeglImageOES>(mEGLImage),
-                                          nullptr);
-        } else {
-            glEGLImageTargetTexture2DOES(mTexTarget, static_cast<GLeglImageOES>(mEGLImage));
-        }
-    } else {
+    if (desc.stride & kGlFormat) {
         int levels = 1;
         if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE) {
             levels = MipLevelCount(desc.width, desc.height);
@@ -894,6 +920,13 @@
                 }
             }
         }
+    } else {
+        if (HasGLExtension("GL_EXT_EGL_image_storage")) {
+            glEGLImageTargetTexStorageEXT(mTexTarget, static_cast<GLeglImageOES>(mEGLImage),
+                                          nullptr);
+        } else {
+            glEGLImageTargetTexture2DOES(mTexTarget, static_cast<GLeglImageOES>(mEGLImage));
+        }
     }
     ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 }
@@ -946,11 +979,11 @@
                 GLuint renderbuffer = 0;
                 glGenRenderbuffers(1, &renderbuffer);
                 glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
-                if (GetParam().stride == 0) {
+                if (GetParam().stride & kGlFormat) {
+                    glRenderbufferStorage(GL_RENDERBUFFER, GetParam().format, width, height);
+                } else {
                     glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER,
                                                            static_cast<GLeglImageOES>(mEGLImage));
-                } else {
-                    glRenderbufferStorage(GL_RENDERBUFFER, GetParam().format, width, height);
                 }
                 glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment_points[i],
                                           GL_RENDERBUFFER, renderbuffer);
@@ -994,9 +1027,9 @@
 
 class AHardwareBufferBlobFormatTest : public AHardwareBufferGLTest {
 public:
-    bool SetUpBuffer(const AHardwareBuffer_Desc& desc, bool use_srgb = false) override {
+    bool SetUpBuffer(const AHardwareBuffer_Desc& desc) override {
         if (!HasGLExtension("GL_EXT_external_buffer")) return false;
-        return AHardwareBufferGLTest::SetUpBuffer(desc, use_srgb);
+        return AHardwareBufferGLTest::SetUpBuffer(desc);
     }
 };
 
@@ -1135,7 +1168,17 @@
         AHardwareBuffer_Desc{1, 1, 1, AHARDWAREBUFFER_FORMAT_BLOB, 0, 0, 0, 0}));
 
 
-class AHardwareBufferColorFormatTest : public AHardwareBufferGLTest {};
+class AHardwareBufferColorFormatTest : public AHardwareBufferGLTest {
+public:
+    bool SetUpBuffer(const AHardwareBuffer_Desc& desc) override {
+        if ((desc.usage & AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT) &&
+            !IsFormatColorRenderable(desc.format, desc.stride & kUseSrgb)) {
+            ALOGI("Test skipped: requires GPU_COLOR_OUTPUT, but format is not color-renderable");
+            return false;
+        }
+        return AHardwareBufferGLTest::SetUpBuffer(desc);
+    }
+};
 
 // Verify that when allocating an AHardwareBuffer succeeds with GPU_COLOR_OUTPUT,
 // it can be bound as a framebuffer attachment, glClear'ed and then read from
@@ -1161,10 +1204,10 @@
 
     MakeCurrent(0);
     std::vector<GoldenPixel> goldens{
-        {10, 90, kRed},  {40, 90, kRed},  {60, 90, kBlue}, {90, 90, kBlue},
-        {10, 60, kRed},  {40, 60, kRed},  {60, 60, kBlue}, {90, 60, kBlue},
-        {10, 40, kZero}, {40, 40, kZero}, {60, 40, kRed},  {90, 40, kRed},
-        {10, 10, kZero}, {40, 10, kZero}, {60, 10, kRed},  {90, 10, kRed},
+        {10, 90, kRed},  {40, 90, kRed},  {60, 90, kBlue},  {90, 90, kBlue},
+        {10, 60, kRed},  {40, 60, kRed},  {60, 60, kBlue},  {90, 60, kBlue},
+        {10, 40, kZero}, {40, 40, kZero}, {60, 40, kGreen}, {90, 40, kGreen},
+        {10, 10, kZero}, {40, 10, kZero}, {60, 10, kGreen}, {90, 10, kGreen},
     };
     CheckGoldenPixels(goldens, desc.format);
 }
@@ -1176,7 +1219,7 @@
     desc.height = 10;
     desc.usage = AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT | AHARDWAREBUFFER_USAGE_CPU_READ_RARELY;
     // This test does not make sense for GL formats. Layered buffers do not support CPU access.
-    if (desc.stride != 0 || desc.layers > 1) return;
+    if ((desc.stride & kGlFormat) || desc.layers > 1) return;
     if (!SetUpBuffer(desc)) return;
 
     MakeCurrent(1);
@@ -1195,10 +1238,10 @@
     ASSERT_EQ(NO_ERROR, result);
 
     std::vector<GoldenPixel> goldens{
-        {0, 9, kRed},  {4, 9, kRed},  {5, 9, kBlue}, {9, 9, kBlue},
-        {0, 5, kRed},  {4, 5, kRed},  {5, 5, kBlue}, {9, 5, kBlue},
-        {0, 4, kZero}, {4, 4, kZero}, {5, 4, kRed},  {9, 4, kRed},
-        {0, 0, kZero}, {4, 0, kZero}, {5, 0, kRed},  {9, 0, kRed},
+        {0, 9, kRed},  {4, 9, kRed},  {5, 9, kBlue},  {9, 9, kBlue},
+        {0, 5, kRed},  {4, 5, kRed},  {5, 5, kBlue},  {9, 5, kBlue},
+        {0, 4, kZero}, {4, 4, kZero}, {5, 4, kGreen}, {9, 4, kGreen},
+        {0, 0, kZero}, {4, 0, kZero}, {5, 0, kGreen}, {9, 0, kGreen},
     };
     for (const GoldenPixel& golden : goldens) {
         ptrdiff_t row_offset = golden.y * desc.stride;
@@ -1226,9 +1269,9 @@
                 uint16_t* pixel = reinterpret_cast<uint16_t*>(
                     reinterpret_cast<uint8_t*>(data) + (row_offset + golden.x) * 2);
                 std::array<uint8_t, 4> pixel_to_check = {
-                    static_cast<uint8_t>(((*pixel & 0xF800) >> 11) * (255.f/31.f)),
-                    static_cast<uint8_t>(((*pixel & 0x07E0) >> 5) * (255.f/63.f)),
-                    static_cast<uint8_t>((*pixel & 0x001F) * (255.f/31.f)),
+                    static_cast<uint8_t>(((*pixel & 0xF800) >> 11) * (255./31.)),
+                    static_cast<uint8_t>(((*pixel & 0x07E0) >> 5) * (255./63.)),
+                    static_cast<uint8_t>((*pixel & 0x001F) * (255./31.)),
                     255,
                 };
                 CheckGoldenPixel(golden, pixel_to_check, desc.format);
@@ -1248,10 +1291,10 @@
                 uint32_t* pixel = reinterpret_cast<uint32_t*>(
                     reinterpret_cast<uint8_t*>(data) + (row_offset + golden.x) * 4);
                 std::array<uint8_t, 4> pixel_to_check = {
-                    static_cast<uint8_t>((*pixel & 0x000003FF) * (255.f/1023.f)),
-                    static_cast<uint8_t>(((*pixel & 0x000FFC00) >> 10) * (255.f/1023.f)),
-                    static_cast<uint8_t>(((*pixel & 0x3FF00000) >> 20) * (255.f/1023.f)),
-                    static_cast<uint8_t>(((*pixel & 0xC0000000) >> 30) * (255.f/3.f)),
+                    static_cast<uint8_t>((*pixel & 0x000003FF) * (255./1023.)),
+                    static_cast<uint8_t>(((*pixel & 0x000FFC00) >> 10) * (255./1023.)),
+                    static_cast<uint8_t>(((*pixel & 0x3FF00000) >> 20) * (255./1023.)),
+                    static_cast<uint8_t>(((*pixel & 0xC0000000) >> 30) * (255./3.)),
                 };
                 CheckGoldenPixel(golden, pixel_to_check, desc.format);
                 break;
@@ -1296,10 +1339,14 @@
     EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check the rendered pixels. There should be a red square in the middle.
+    GoldenColor color = kRed;
+    if (desc.stride & kUseSrgb) {
+        color = FormatHasAlpha(desc.format) ? kRed50 : kRed50Alpha100;
+    }
     std::vector<GoldenPixel> goldens{
         {5, 35, kZero}, {15, 35, kZero}, {25, 35, kZero}, {35, 35, kZero},
-        {5, 25, kZero}, {15, 25, kRed},  {25, 25, kRed},  {35, 25, kZero},
-        {5, 15, kZero}, {15, 15, kRed},  {25, 15, kRed},  {35, 15, kZero},
+        {5, 25, kZero}, {15, 25, color}, {25, 25, color}, {35, 25, kZero},
+        {5, 15, kZero}, {15, 15, color}, {25, 15, color}, {35, 15, kZero},
         {5,  5, kZero}, {15,  5, kZero}, {25, 5,  kZero}, {35, 5,  kZero},
     };
     CheckGoldenPixels(goldens, GL_RGBA8);
@@ -1344,10 +1391,10 @@
     // format has an alpha channel.
     const GoldenColor kCBBlack = FormatHasAlpha(desc.format) ? kZero : kBlack;
     std::vector<GoldenPixel> goldens{
-        {5, 35, kZero}, {15, 35, kZero},    {25, 35, kZero}, {35, 35, kZero},
-        {5, 25, kZero}, {15, 25, kRed},     {25, 25, kBlue}, {35, 25, kZero},
-        {5, 15, kZero}, {15, 15, kCBBlack}, {25, 15, kRed},  {35, 15, kZero},
-        {5, 5,  kZero}, {15, 5,  kZero},    {25, 5,  kZero}, {35, 5,  kZero},
+        {5, 35, kZero}, {15, 35, kZero},    {25, 35, kZero},  {35, 35, kZero},
+        {5, 25, kZero}, {15, 25, kRed},     {25, 25, kBlue},  {35, 25, kZero},
+        {5, 15, kZero}, {15, 15, kCBBlack}, {25, 15, kGreen}, {35, 15, kZero},
+        {5, 5,  kZero}, {15, 5,  kZero},    {25, 5,  kZero},  {35, 5,  kZero},
     };
     CheckGoldenPixels(goldens, GL_RGBA8);
 }
@@ -1363,10 +1410,7 @@
         AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT |
         AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
         AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE;
-    const bool kUseSrgb = desc.format == GL_SRGB8_ALPHA8 ||
-        desc.format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM ||
-        desc.format == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
-    if (!SetUpBuffer(desc, kUseSrgb)) return;
+    if (!SetUpBuffer(desc)) return;
 
     const int kTextureUnit = 7;
     for (int i = 0; i < mContextCount; ++i) {
@@ -1395,7 +1439,7 @@
     MakeCurrent(0);
     SetUpFramebuffer(1, 1, desc.layers - 1, kBufferAsTexture, kNone, kNone, kNone,
                      MipLevelCount(desc.width, desc.height) - 1);
-    std::vector<GoldenPixel> goldens{{0, 0, kUseSrgb ? kRed50Srgb : kRed50}};
+    std::vector<GoldenPixel> goldens{{0, 0, (desc.stride & kUseSrgb) ? kRed50Srgb : kRed50}};
     CheckGoldenPixels(goldens, desc.format);
 }
 
@@ -1439,10 +1483,10 @@
 
         const GoldenColor kCBBlack = FormatHasAlpha(desc.format) ? kZero : kBlack;
         std::vector<GoldenPixel> goldens{
-            {5, 35, kZero}, {15, 35, kZero},    {25, 35, kZero}, {35, 35, kZero},
-            {5, 25, kZero}, {15, 25, kRed},     {25, 25, kBlue}, {35, 25, kZero},
-            {5, 15, kZero}, {15, 15, kCBBlack}, {25, 15, kRed},  {35, 15, kZero},
-            {5, 5,  kZero}, {15, 5,  kZero},    {25, 5,  kZero}, {35, 5,  kZero},
+            {5, 35, kZero}, {15, 35, kZero},    {25, 35, kZero},  {35, 35, kZero},
+            {5, 25, kZero}, {15, 25, kRed},     {25, 25, kBlue},  {35, 25, kZero},
+            {5, 15, kZero}, {15, 15, kCBBlack}, {25, 15, kGreen}, {35, 15, kZero},
+            {5, 5,  kZero}, {15, 5,  kZero},    {25, 5,  kZero},  {35, 5,  kZero},
         };
         CheckGoldenPixels(goldens, GL_RGBA8);
     }
@@ -1461,10 +1505,7 @@
     desc.width = (desc.width / kNumTiles) * kNumTiles;
     desc.height = desc.width;
     desc.layers *= 6;
-    const bool kUseSrgb = desc.format == GL_SRGB8_ALPHA8 ||
-        desc.format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM ||
-        desc.format == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
-    if (!SetUpBuffer(desc, kUseSrgb)) return;
+    if (!SetUpBuffer(desc)) return;
 
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
@@ -1492,23 +1533,27 @@
     for (int face = 0; face < 6; ++face) {
         SetUpFramebuffer(1, 1, desc.layers - 6 + face, kBufferAsTexture, kNone, kNone, kNone,
                          MipLevelCount(desc.width, desc.height) - 1);
-        std::vector<GoldenPixel> goldens{{0, 0, kUseSrgb ? kRed50Srgb : kRed50}};
+        std::vector<GoldenPixel> goldens{{0, 0, (desc.stride & kUseSrgb) ? kRed50Srgb : kRed50}};
         CheckGoldenPixels(goldens, desc.format);
     }
 }
 
+// The 'stride' field is used to pass a combination of TestFlags.
 INSTANTIATE_TEST_CASE_P(
     SingleLayer,
     AHardwareBufferColorFormatTest,
     ::testing::Values(
-        AHardwareBuffer_Desc{75, 33, 1, GL_RGB8, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{64, 80, 1, GL_RGBA8, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{49, 23, 1, GL_SRGB8_ALPHA8, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{42, 41, 1, GL_RGBA16F, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{37, 63, 1, GL_RGB10_A2, 0, 1, 0, 0},
+        AHardwareBuffer_Desc{75, 33, 1, GL_RGB8, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{64, 80, 1, GL_RGBA8, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{49, 23, 1, GL_SRGB8_ALPHA8, 0, kGlFormat | kUseSrgb, 0, 0},
+        AHardwareBuffer_Desc{42, 41, 1, GL_RGBA16F, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{37, 63, 1, GL_RGB10_A2, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{33, 20, 1, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, 0, 0, 0},
+        AHardwareBuffer_Desc{33, 20, 1, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, kUseSrgb, 0, 0},
         AHardwareBuffer_Desc{20, 10, 1, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM, 0, 0, 0, 0},
+        AHardwareBuffer_Desc{20, 10, 1, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM, 0, kUseSrgb, 0, 0},
         AHardwareBuffer_Desc{16, 20, 1, AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM, 0, 0, 0, 0},
+        AHardwareBuffer_Desc{16, 20, 1, AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM, 0, kUseSrgb, 0, 0},
         AHardwareBuffer_Desc{10, 20, 1, AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM, 0, 0, 0, 0},
         AHardwareBuffer_Desc{10, 20, 1, AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT, 0, 0, 0, 0},
         AHardwareBuffer_Desc{10, 20, 1, AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM, 0, 0, 0, 0}));
@@ -1517,15 +1562,17 @@
     MultipleLayers,
     AHardwareBufferColorFormatTest,
     ::testing::Values(
-        AHardwareBuffer_Desc{75, 33, 5, GL_RGB8, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{64, 80, 6, GL_RGBA8, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{33, 28, 4, GL_SRGB8_ALPHA8, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{42, 41, 3, GL_RGBA16F, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{37, 63, 4, GL_RGB10_A2, 0, 1, 0, 0},
+        AHardwareBuffer_Desc{75, 33, 5, GL_RGB8, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{64, 80, 6, GL_RGBA8, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{33, 28, 4, GL_SRGB8_ALPHA8, 0, kGlFormat | kUseSrgb, 0, 0},
+        AHardwareBuffer_Desc{42, 41, 3, GL_RGBA16F, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{37, 63, 4, GL_RGB10_A2, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{25, 77, 7, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, 0, 0, 0},
-        AHardwareBuffer_Desc{64, 64, 4, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, 0, 0, 0},
+        AHardwareBuffer_Desc{25, 77, 7, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, kUseSrgb, 0, 0},
         AHardwareBuffer_Desc{30, 30, 3, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM, 0, 0, 0, 0},
+        AHardwareBuffer_Desc{30, 30, 3, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM, 0, kUseSrgb, 0, 0},
         AHardwareBuffer_Desc{50, 50, 4, AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM, 0, 0, 0, 0},
+        AHardwareBuffer_Desc{50, 50, 4, AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM, 0, kUseSrgb, 0, 0},
         AHardwareBuffer_Desc{20, 10, 2, AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM, 0, 0, 0, 0},
         AHardwareBuffer_Desc{20, 20, 4, AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT, 0, 0, 0, 0},
         AHardwareBuffer_Desc{30, 20, 16, AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM, 0, 0, 0, 0}));
@@ -1569,7 +1616,7 @@
     std::vector<GoldenPixel> goldens{
         {5, 35, kRed}, {15, 35, kRed},  {25, 35, kZero}, {35, 35, kZero},
         {5, 25, kRed}, {15, 25, kZero}, {25, 25, kZero}, {35, 25, kZero},
-        {5, 15, kRed}, {15, 15, kRed},  {25, 15, kZero}, {35, 15, kRed},
+        {5, 15, kRed}, {15, 15, kRed},  {25, 15, kRed},  {35, 15, kRed},
         {5, 5,  kRed}, {15, 5,  kRed},  {25, 5,  kRed},  {35, 5,  kRed},
     };
     CheckGoldenPixels(goldens, GL_RGBA8);
@@ -1684,12 +1731,12 @@
     }
 }
 
-// See comment in SetUpBuffer for explanation of nonzero stride and GL format.
+// The 'stride' field is used to pass a combination of TestFlags.
 INSTANTIATE_TEST_CASE_P(
     SingleLayer,
     AHardwareBufferDepthFormatTest,
     ::testing::Values(
-        AHardwareBuffer_Desc{16, 24, 1, GL_DEPTH_COMPONENT16, 0, 1, 0, 0},
+        AHardwareBuffer_Desc{16, 24, 1, GL_DEPTH_COMPONENT16, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{16, 24, 1, AHARDWAREBUFFER_FORMAT_D16_UNORM, 0, 0, 0, 0},
         AHardwareBuffer_Desc{44, 21, 1, AHARDWAREBUFFER_FORMAT_D24_UNORM, 0, 0, 0, 0},
         AHardwareBuffer_Desc{57, 33, 1, AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT, 0, 0, 0, 0},
@@ -1701,7 +1748,7 @@
     MultipleLayers,
     AHardwareBufferDepthFormatTest,
     ::testing::Values(
-        AHardwareBuffer_Desc{16, 24, 6, GL_DEPTH_COMPONENT16, 0, 1, 0, 0},
+        AHardwareBuffer_Desc{16, 24, 6, GL_DEPTH_COMPONENT16, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{16, 24, 6, AHARDWAREBUFFER_FORMAT_D16_UNORM, 0, 0, 0, 0},
         AHardwareBuffer_Desc{44, 21, 4, AHARDWAREBUFFER_FORMAT_D24_UNORM, 0, 0, 0, 0},
         AHardwareBuffer_Desc{57, 33, 7, AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT, 0, 0, 0, 0},
@@ -1746,14 +1793,18 @@
     glStencilFunc(GL_EQUAL, 2, 0xFF);
     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
     glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
+    glUniform4f(mColorLocation, 0.f, 1.f, 0.f, 1.f);
+    glStencilFunc(GL_EQUAL, 4, 0xFF);
+    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+    glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
     EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check golden pixels.
     std::vector<GoldenPixel> goldens{
-        {5, 35, kRed},  {15, 35, kRed},  {25, 35, kZero}, {35, 35, kZero},
-        {5, 25, kRed},  {15, 25, kRed},  {25, 25, kZero}, {35, 25, kZero},
-        {5, 15, kZero}, {15, 15, kZero}, {25, 15, kRed},  {35, 15, kRed},
-        {5, 5,  kZero}, {15, 5,  kZero}, {25, 5,  kRed},  {35, 5,  kRed},
+        {5, 35, kRed},  {15, 35, kRed},  {25, 35, kZero},  {35, 35, kZero},
+        {5, 25, kRed},  {15, 25, kRed},  {25, 25, kZero},  {35, 25, kZero},
+        {5, 15, kZero}, {15, 15, kZero}, {25, 15, kGreen}, {35, 15, kGreen},
+        {5, 5,  kZero}, {15, 5,  kZero}, {25, 5,  kGreen}, {35, 5,  kGreen},
     };
     CheckGoldenPixels(goldens, GL_RGBA8);
 }
@@ -1801,21 +1852,21 @@
 
     // Check golden pixels.
     std::vector<GoldenPixel> goldens{
-        {5, 35, kRed},  {15, 35, kRed},  {25, 35, kBlue}, {35, 35, kBlue},
-        {5, 25, kRed},  {15, 25, kRed},  {25, 25, kBlue}, {35, 25, kBlue},
-        {5, 15, kZero}, {15, 15, kZero}, {25, 15, kRed},  {35, 15, kRed},
-        {5, 5,  kZero}, {15, 5,  kZero}, {25, 5,  kRed},  {35, 5,  kRed},
+        {5, 35, kRed},  {15, 35, kRed},  {25, 35, kBlue},  {35, 35, kBlue},
+        {5, 25, kRed},  {15, 25, kRed},  {25, 25, kBlue},  {35, 25, kBlue},
+        {5, 15, kZero}, {15, 15, kZero}, {25, 15, kGreen}, {35, 15, kGreen},
+        {5, 5,  kZero}, {15, 5,  kZero}, {25, 5,  kGreen}, {35, 5,  kGreen},
     };
     CheckGoldenPixels(goldens, GL_RGBA8);
 }
 
-// See comment in SetUpBuffer for explanation of nonzero stride and GL format.
+// The 'stride' field is used to pass a combination of TestFlags.
 INSTANTIATE_TEST_CASE_P(
     SingleLayer,
     AHardwareBufferStencilFormatTest,
     ::testing::Values(
-        AHardwareBuffer_Desc{49, 57, 1, GL_STENCIL_INDEX8, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{36, 50, 1, GL_DEPTH24_STENCIL8, 0, 1, 0, 0},
+        AHardwareBuffer_Desc{49, 57, 1, GL_STENCIL_INDEX8, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{36, 50, 1, GL_DEPTH24_STENCIL8, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{26, 29, 1, AHARDWAREBUFFER_FORMAT_S8_UINT, 0, 0, 0, 0},
         AHardwareBuffer_Desc{57, 33, 1, AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT, 0, 0, 0, 0},
         AHardwareBuffer_Desc{17, 23, 1, AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT, 0, 0, 0, 0}));
@@ -1824,8 +1875,8 @@
     MultipleLayers,
     AHardwareBufferStencilFormatTest,
     ::testing::Values(
-        AHardwareBuffer_Desc{49, 57, 3, GL_STENCIL_INDEX8, 0, 1, 0, 0},
-        AHardwareBuffer_Desc{36, 50, 6, GL_DEPTH24_STENCIL8, 0, 1, 0, 0},
+        AHardwareBuffer_Desc{49, 57, 3, GL_STENCIL_INDEX8, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{36, 50, 6, GL_DEPTH24_STENCIL8, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{26, 29, 5, AHARDWAREBUFFER_FORMAT_S8_UINT, 0, 0, 0, 0},
         AHardwareBuffer_Desc{57, 33, 4, AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT, 0, 0, 0, 0},
         AHardwareBuffer_Desc{17, 23, 7, AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT, 0, 0, 0, 0}));
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
index c435a8a..1a08d3d 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -70,7 +70,7 @@
     private static final int STATE_WIFI_ENABLED = 2;
     private static final int STATE_WIFI_DISABLED = 3;
     private static final int STATE_SCANNING = 4;
-    private static final int STATE_SCAN_RESULTS_AVAILABLE = 5;
+    private static final int STATE_SCAN_DONE = 5;
 
     private static final String TAG = "WifiManagerTest";
     private static final String SSID1 = "\"WifiManagerTest\"";
@@ -96,13 +96,15 @@
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+
                 synchronized (mMySync) {
-                    if (mWifiManager.getScanResults() != null) {
+                    if (intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)) {
                         mScanResults = mWifiManager.getScanResults();
-                        mMySync.expectedState = STATE_SCAN_RESULTS_AVAILABLE;
-                        mScanResults = mWifiManager.getScanResults();
-                        mMySync.notifyAll();
+                    } else {
+                        mScanResults = null;
                     }
+                    mMySync.expectedState = STATE_SCAN_DONE;
+                    mMySync.notifyAll();
                 }
             } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
                 int newState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
diff --git a/tests/tests/os/AndroidTest.xml b/tests/tests/os/AndroidTest.xml
index 680e88b..8f8d997 100644
--- a/tests/tests/os/AndroidTest.xml
+++ b/tests/tests/os/AndroidTest.xml
@@ -23,6 +23,11 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.os.cts" />
         <option name="runtime-hint" value="3m15s" />
+        <!-- Do not disable hidden-api-checks for this test. This test includes
+             StrictModeTest which relies on hidden API checks being enabled in
+             order to function properly. Any APIs used by this test should be
+             added to the light grey list, or made @TestApi instead.
         <option name="hidden-api-checks" value="false" />
+        -->
     </test>
 </configuration>
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index cee7da6..39ca729 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -3941,6 +3941,11 @@
     <permission android:name="android.permission.OPEN_APPLICATION_DETAILS_OPEN_BY_DEFAULT_PAGE"
                 android:protectionLevel="signature" />
 
+    <!-- Allows hidden API checks to be disabled when starting a process.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.DISABLE_HIDDEN_API_CHECKS"
+                android:protectionLevel="signature" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/tests/tests/security/res/raw/cve_2017_13309_client.bks b/tests/tests/security/res/raw/cve_2017_13309_client.bks
new file mode 100644
index 0000000..6f450d3
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2017_13309_client.bks
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2017_13309_server.bks b/tests/tests/security/res/raw/cve_2017_13309_server.bks
new file mode 100644
index 0000000..384c4be
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2017_13309_server.bks
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2017_13309_trustedcert.bks b/tests/tests/security/res/raw/cve_2017_13309_trustedcert.bks
new file mode 100644
index 0000000..539d36e
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2017_13309_trustedcert.bks
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java b/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java
new file mode 100644
index 0000000..8f6477e
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java
@@ -0,0 +1,866 @@
+/*
+ * Copyright (C) 2018 The AndroCid Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.security.cts;
+
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import android.platform.test.annotations.SecurityTest;
+import android.test.InstrumentationTestCase;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.ByteArrayInputStream;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.spi.SelectorProvider;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Formatter;
+import java.util.Iterator;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManagerFactory;
+
+public class SSLConscryptPlainTextExposureTest extends InstrumentationTestCase {
+
+  private TestSSLServer mTestSSLServer;
+  private TestSSLConnection mTestSSLClient;
+  public static Context context;
+  public static String output = "";
+  private final String pattern = ".*PLAIN TEXT EXPOSED.*";
+
+  public void test_android_CVE_2017_13309() {
+
+    context = getInstrumentation().getContext();
+    mTestSSLServer = new TestSSLServer();
+
+    try {
+      Thread.sleep(1000);
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    mTestSSLClient = new TestSSLConnection();
+    mTestSSLClient.StartConnect();
+    assertFalse("Pattern found",
+        Pattern.compile(pattern,
+          Pattern.DOTALL).matcher(output).matches());
+  }
+
+  public static InputStream getResource(final int resource){
+    return SSLConscryptPlainTextExposureTest.context.getResources().openRawResource(resource);
+  }
+
+  public static void setOutput(String data){
+    SSLConscryptPlainTextExposureTest.output = data;
+  }
+}
+
+
+class TestSSLConnection {
+
+  public SSLContext sslc;
+
+  public SocketChannel socketChannel;
+  public SSLEngine clientEngine;
+  public String remoteAddress = "127.0.0.1";
+  public int port = 9000;
+  public ByteBuffer[] dataOutAppBuffers = new ByteBuffer[3];
+  public ByteBuffer dataOutNetBuffer;
+  public ByteBuffer hsInAppBuffer, hsInNetBuffer, hsOutAppBuffer, hsOutNetBuffer;
+  public boolean isHandshaked = false;
+  public ExecutorService executor = Executors.newSingleThreadExecutor();
+  public InputStream clientKey = null;
+  public InputStream trustedCert = null;
+
+  public void StartConnect() {
+    KeyStore ks = null;
+
+    clientKey = SSLConscryptPlainTextExposureTest.getResource(R.raw.cve_2017_13309_client);
+    trustedCert = SSLConscryptPlainTextExposureTest.getResource(R.raw.cve_2017_13309_trustedcert);
+
+    try {
+      ks = KeyStore.getInstance(KeyStore.getDefaultType());
+    } catch (KeyStoreException e) {
+      e.printStackTrace();
+    }
+    KeyStore ts = null;
+    try {
+      ts = KeyStore.getInstance(KeyStore.getDefaultType());
+    } catch (KeyStoreException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      ks.load(clientKey, "pocclient".toCharArray());
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    try {
+      ts.load(trustedCert, "trusted".toCharArray());
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    KeyManagerFactory kmf = null;
+    try {
+      kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+    } catch (NoSuchAlgorithmException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      kmf.init(ks, "keypass".toCharArray());
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    TrustManagerFactory tmf = null;
+    try {
+      tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+    } catch (NoSuchAlgorithmException e) {
+      e.printStackTrace();
+    }
+    try {
+      tmf.init(ts);
+    } catch (KeyStoreException e) {
+      e.printStackTrace();
+    }
+
+    SSLContext sslCtx = null;
+    try {
+      sslCtx = SSLContext.getInstance("TLSv1.2");
+    } catch (NoSuchAlgorithmException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+    } catch (KeyManagementException e) {
+      e.printStackTrace();
+    }
+
+    sslc = sslCtx;
+
+    clientEngine = sslc.createSSLEngine(remoteAddress, port);
+    clientEngine.setUseClientMode(true);
+    SSLSession session = clientEngine.getSession();
+
+    hsOutAppBuffer = ByteBuffer.allocate(4096);
+    hsOutNetBuffer = ByteBuffer.allocate(session.getPacketBufferSize());
+    hsInAppBuffer = ByteBuffer.allocate(4096);
+    hsInNetBuffer = ByteBuffer.allocate(session.getPacketBufferSize());
+    dataOutNetBuffer = ByteBuffer.allocate(session.getPacketBufferSize());
+
+    try {
+      socketChannel = SocketChannel.open();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    try {
+      socketChannel.configureBlocking(false);
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    try {
+      socketChannel.connect(new InetSocketAddress(remoteAddress, port));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      while(!socketChannel.finishConnect()) {
+      }
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      clientEngine.beginHandshake();
+    } catch (SSLException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      isHandshaked = doHandshake(socketChannel, clientEngine);
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    if(isHandshaked) {
+      dataOutAppBuffers[0] = ByteBuffer.wrap("PLAIN TEXT EXPOSED".getBytes());
+      dataOutAppBuffers[1] = ByteBuffer.wrap("PLAIN TEXT EXPOSED".getBytes());
+      dataOutAppBuffers[2] = ByteBuffer.wrap("PLAIN TEXT EXPOSED".getBytes());
+
+      while(dataOutAppBuffers[0].hasRemaining() || dataOutAppBuffers[1].hasRemaining() || dataOutAppBuffers[2].hasRemaining()) {
+        dataOutNetBuffer.clear();
+        SSLEngineResult result = null;
+        try {
+          result = clientEngine.wrap(dataOutAppBuffers, 0, 3, dataOutNetBuffer);
+        } catch (SSLException e) {
+          e.printStackTrace();
+        }
+        switch(result.getStatus()) {
+          case OK:
+            dataOutNetBuffer.flip();
+            String outbuff = new String("");
+            Formatter formatter = new Formatter();
+            for(int i = 0; i < dataOutNetBuffer.limit(); i++) {
+              outbuff += formatter.format("%02x ", dataOutNetBuffer.get(i)).toString();
+            }
+            String output = new String(dataOutNetBuffer.array());
+            SSLConscryptPlainTextExposureTest.setOutput(output);
+            break;
+          case BUFFER_OVERFLOW:
+            dataOutNetBuffer = enlargePacketBuffer(clientEngine, dataOutNetBuffer);
+            break;
+          case BUFFER_UNDERFLOW:
+            try {
+              throw new SSLException("Buffer underflow in sending data");
+            } catch (SSLException e) {
+              e.printStackTrace();
+            }
+          case CLOSED:
+            try {
+              closeConnection(socketChannel, clientEngine);
+            } catch (IOException e) {
+              e.printStackTrace();
+            }
+            return;
+          default:
+            throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+        }
+      }
+    }
+
+    try {
+      clientKey.close();
+    } catch (IOException e){
+      e.printStackTrace();
+    }
+
+    try{
+      trustedCert.close();
+    } catch (IOException e){
+      e.printStackTrace();
+    }
+
+    try {
+      shutdown();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+  }
+
+  public void shutdown() throws IOException {
+    closeConnection(socketChannel, clientEngine);
+    executor.shutdown();
+  }
+
+  public void closeConnection(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+    engine.closeOutbound();
+    doHandshake(socketChannel, engine);
+    socketChannel.close();
+  }
+
+  public boolean doHandshake(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+    SSLEngineResult result;
+    SSLEngineResult.HandshakeStatus handshakeStatus;
+    int appBufferSize = engine.getSession().getApplicationBufferSize();
+
+    ByteBuffer srcAppData = ByteBuffer.allocate(appBufferSize);
+    ByteBuffer dstAppData = ByteBuffer.allocate(appBufferSize);
+
+    srcAppData.clear();
+    dstAppData.clear();
+
+    handshakeStatus = engine.getHandshakeStatus();
+    while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED
+        && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+      switch(handshakeStatus) {
+        case NEED_UNWRAP:
+          if(socketChannel.read(hsInNetBuffer) < 0) {
+            if(engine.isInboundDone() && engine.isOutboundDone()) {
+              return false;
+            }
+            try {
+              engine.closeInbound();
+            } catch (SSLException e) {
+              Log.e("poc-test","Forced to close inbound. No proper SSL/TLS close notification message from peer");
+            }
+            engine.closeOutbound();
+            handshakeStatus = engine.getHandshakeStatus();
+            break;
+          }
+          hsInNetBuffer.flip();
+          try {
+            result = engine.unwrap(hsInNetBuffer, hsInAppBuffer);
+            hsInNetBuffer.compact();
+            handshakeStatus = result.getHandshakeStatus();
+          } catch (SSLException sslException) {
+            engine.closeOutbound();
+            handshakeStatus = engine.getHandshakeStatus();
+            break;
+          }
+          switch(result.getStatus()) {
+            case OK:
+              break;
+            case BUFFER_OVERFLOW:
+              hsInAppBuffer = enlargeApplicationBuffer(engine, hsInAppBuffer);
+              break;
+            case BUFFER_UNDERFLOW:
+              hsInNetBuffer = handleBufferUnderflow(engine, hsInNetBuffer);
+              break;
+            case CLOSED:
+              if (engine.isOutboundDone()) {
+                return false;
+              } else {
+                engine.closeOutbound();
+                handshakeStatus = engine.getHandshakeStatus();
+                break;
+              }
+            default:
+              throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+          }
+        case NEED_WRAP:
+          hsOutNetBuffer.clear();
+          try {
+            result = engine.wrap(hsOutAppBuffer, hsOutNetBuffer);
+            handshakeStatus = result.getHandshakeStatus();
+          } catch (SSLException sslException) {
+            engine.closeOutbound();
+            handshakeStatus = engine.getHandshakeStatus();
+            break;
+          }
+          switch(result.getStatus()) {
+            case OK:
+              hsOutNetBuffer.flip();
+              while(hsOutNetBuffer.hasRemaining()) {
+                socketChannel.write(hsOutNetBuffer);
+              }
+              break;
+            case BUFFER_OVERFLOW:
+              hsOutNetBuffer = enlargePacketBuffer(engine, hsOutNetBuffer);
+              break;
+            case BUFFER_UNDERFLOW:
+              throw new SSLException("Buffer underflow in handshake and wrap");
+            case CLOSED:
+              try {
+                hsOutNetBuffer.flip();
+                while(hsOutNetBuffer.hasRemaining()) {
+                  socketChannel.write(hsOutNetBuffer);
+                }
+                hsInNetBuffer.clear();
+              } catch (Exception e) {
+                handshakeStatus = engine.getHandshakeStatus();
+              }
+              break;
+            default:
+              throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+          }
+          break;
+        case NEED_TASK:
+          Runnable task;
+          while((task = engine.getDelegatedTask()) != null) {
+            executor.execute(task);
+          }
+          handshakeStatus = engine.getHandshakeStatus();
+          break;
+        case FINISHED:
+          break;
+        case NOT_HANDSHAKING:
+          break;
+        default:
+          throw new IllegalStateException("Invalid SSL status: " + handshakeStatus);
+      }
+        }
+    return true;
+  }
+
+  public ByteBuffer enlargePacketBuffer(SSLEngine engine, ByteBuffer buffer) {
+    return enlargeBuffer(buffer, engine.getSession().getPacketBufferSize());
+  }
+
+  public ByteBuffer enlargeApplicationBuffer(SSLEngine engine, ByteBuffer buffer) {
+    return enlargeBuffer(buffer, engine.getSession().getApplicationBufferSize());
+  }
+
+  public ByteBuffer enlargeBuffer(ByteBuffer buffer, int bufferSize) {
+    if(bufferSize > buffer.capacity()) {
+      buffer = ByteBuffer.allocate(bufferSize);
+    }
+    else {
+      buffer = ByteBuffer.allocate(buffer.capacity() * 2);
+    }
+    return buffer;
+  }
+  public ByteBuffer handleBufferUnderflow(SSLEngine engine, ByteBuffer buffer) {
+    if(engine.getSession().getPacketBufferSize() < buffer.limit()) {
+      return buffer;
+    }
+    else {
+      ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer);
+      buffer.flip();
+      replaceBuffer.put(buffer);
+      return replaceBuffer;
+    }
+  }
+}
+
+class TestSSLServer {
+
+  public ServerRunnable serverRunnable;
+  Thread server;
+
+  public TestSSLServer() {
+    serverRunnable = new ServerRunnable();
+    server = new Thread(serverRunnable);
+    server.start();
+
+    try{
+      Thread.sleep(1000);
+    }catch(InterruptedException e){
+      e.printStackTrace();
+    }
+  }
+
+  protected void onCancelled(String result) {
+    serverRunnable.stop();
+  }
+}
+
+class ServerRunnable implements Runnable {
+
+  SSLServer server;
+  InputStream serverKey = null;
+  InputStream trustedCert = null;
+
+  public void run() {
+    try {
+      server = new SSLServer();
+      server.runServer();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  public void stop() {
+    server.stopServer();
+  }
+}
+
+class SSLServer {
+  public SSLContext serverContext;
+
+  public ByteBuffer hsInAppBuffer, hsInNetBuffer, hsOutAppBuffer, hsOutNetBuffer;
+  public ByteBuffer dataInAppBuffer, dataInNetBuffer;
+
+  final String hostAddress = "127.0.0.1";
+  public int port = 9000;
+  public boolean bActive = false;
+
+  public Selector selector;
+  InputStream serverKey = null;
+  InputStream trustedCert = null;
+  public ExecutorService executor = Executors.newSingleThreadExecutor();
+
+  public void stopServer() {
+    bActive = false;
+    executor.shutdown();
+    selector.wakeup();
+  }
+
+  public void runServer() {
+    KeyStore ks = null;
+
+    serverKey = SSLConscryptPlainTextExposureTest.getResource(R.raw.cve_2017_13309_server);
+    trustedCert = SSLConscryptPlainTextExposureTest.getResource(R.raw.cve_2017_13309_trustedcert);
+
+    try {
+      ks = KeyStore.getInstance(KeyStore.getDefaultType());
+    } catch (KeyStoreException e) {
+      e.printStackTrace();
+    }
+    KeyStore ts = null;
+    try {
+      ts = KeyStore.getInstance(KeyStore.getDefaultType());
+    } catch (KeyStoreException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      ks.load(serverKey, "pocserver".toCharArray());
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    try {
+      ts.load(trustedCert, "trusted".toCharArray());
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    KeyManagerFactory kmf = null;
+    try {
+      kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+    } catch (NoSuchAlgorithmException e) {
+      e.printStackTrace();
+    }
+    try {
+      kmf.init(ks, "keypass".toCharArray());
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    TrustManagerFactory tmf = null;
+    try {
+      tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+    } catch (NoSuchAlgorithmException e) {
+      e.printStackTrace();
+    }
+    try {
+      tmf.init(ts);
+    } catch (KeyStoreException e) {
+      e.printStackTrace();
+    }
+
+    SSLContext sslCtx = null;
+    try {
+      sslCtx = SSLContext.getInstance("TLSv1.2");
+    } catch (NoSuchAlgorithmException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
+    } catch (KeyManagementException e) {
+      e.printStackTrace();
+    }
+
+    serverContext = sslCtx;
+
+    SSLSession dummySession = serverContext.createSSLEngine().getSession();
+
+    hsInAppBuffer = ByteBuffer.allocate(4096);
+    hsInNetBuffer = ByteBuffer.allocate(dummySession.getPacketBufferSize());
+    hsOutAppBuffer = ByteBuffer.allocate(4096);
+    hsOutNetBuffer = ByteBuffer.allocate(dummySession.getPacketBufferSize());
+    dataInAppBuffer = ByteBuffer.allocate(4096);
+    dataInNetBuffer = ByteBuffer.allocate(dummySession.getPacketBufferSize());
+    dummySession.invalidate();
+
+    try {
+      selector = SelectorProvider.provider().openSelector();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    ServerSocketChannel serverSocketChannel = null;
+    try {
+      serverSocketChannel = ServerSocketChannel.open();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    try {
+      serverSocketChannel.configureBlocking(false);
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    try {
+      serverSocketChannel.socket().bind(new InetSocketAddress(hostAddress, port));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    try {
+      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
+    } catch (ClosedChannelException e) {
+      e.printStackTrace();
+    }
+
+    bActive = true;
+
+    while(bActive) {
+      try {
+        selector.select();
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+      Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
+      while (selectedKeys.hasNext()) {
+        SelectionKey key = selectedKeys.next();
+        selectedKeys.remove();
+        if (!key.isValid()) {
+          continue;
+        }
+        if (key.isAcceptable()) {
+          try {
+            accept(key);
+          } catch (Exception e) {
+            e.printStackTrace();
+          }
+        } else if (key.isReadable()) {
+          try {
+            read((SocketChannel) key.channel(), (SSLEngine) key.attachment());
+          } catch (IOException e) {
+            e.printStackTrace();
+          }
+        }
+      }
+    }
+  }
+
+  public void accept(SelectionKey key) throws Exception{
+    SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
+    socketChannel.configureBlocking(false);
+
+    SSLEngine engine = serverContext.createSSLEngine();
+    engine.setUseClientMode(false);
+    engine.beginHandshake();
+
+    socketChannel.register(selector, SelectionKey.OP_READ, engine);
+
+    if(doHandshake(socketChannel, engine)) {
+      socketChannel.register(selector, SelectionKey.OP_READ, engine);
+    }
+    else {
+      socketChannel.close();
+    }
+  }
+
+  public boolean doHandshake(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+    SSLEngineResult result = null;
+    SSLEngineResult.HandshakeStatus handshakeStatus;
+    int appBufferSize = engine.getSession().getApplicationBufferSize();
+
+    ByteBuffer srcAppData = ByteBuffer.allocate(appBufferSize);
+    ByteBuffer dstAppData = ByteBuffer.allocate(appBufferSize);
+
+    srcAppData.clear();
+    dstAppData.clear();
+
+    handshakeStatus = engine.getHandshakeStatus();
+    while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED
+        && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+
+      switch(handshakeStatus) {
+        case NEED_UNWRAP:
+          if(socketChannel.read(hsInNetBuffer) < 0) {
+            if(engine.isInboundDone() && engine.isOutboundDone()) {
+              return false;
+            }
+            try {
+              engine.closeInbound();
+            } catch (SSLException e) {
+              Log.e("server-poc-test","Forced to close inbound. No proper SSL/TLS close notification message from peer");
+            }
+            engine.closeOutbound();
+            handshakeStatus = engine.getHandshakeStatus();
+            break;
+          }
+          hsInNetBuffer.flip();
+          try {
+            result = engine.unwrap(hsInNetBuffer, hsInAppBuffer);
+            hsInNetBuffer.compact();
+            handshakeStatus = result.getHandshakeStatus();
+          } catch (SSLException sslException) {
+            engine.closeOutbound();
+            handshakeStatus = engine.getHandshakeStatus();
+            break;
+          }
+          switch(result.getStatus()) {
+            case OK:
+              break;
+            case BUFFER_OVERFLOW:
+              hsInAppBuffer = enlargeApplicationBuffer(engine, hsInAppBuffer);
+              break;
+            case BUFFER_UNDERFLOW:
+              hsInNetBuffer = handleBufferUnderflow(engine, hsInNetBuffer);
+              break;
+            case CLOSED:
+              if (engine.isOutboundDone()) {
+                return false;
+              } else {
+                engine.closeOutbound();
+                handshakeStatus = engine.getHandshakeStatus();
+                break;
+              }
+            default:
+              throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+          }
+        case NEED_WRAP:
+          hsOutNetBuffer.clear();
+          try {
+            result = engine.wrap(hsOutAppBuffer, hsOutNetBuffer);
+            handshakeStatus = result.getHandshakeStatus();
+          } catch (SSLException sslException) {
+            engine.closeOutbound();
+            handshakeStatus = engine.getHandshakeStatus();
+            break;
+          }
+          switch(result.getStatus()) {
+            case OK:
+              hsOutNetBuffer.flip();
+              while(hsOutNetBuffer.hasRemaining()) {
+                socketChannel.write(hsOutNetBuffer);
+              }
+              break;
+            case BUFFER_OVERFLOW:
+              hsOutNetBuffer = enlargePacketBuffer(engine, hsOutNetBuffer);
+              break;
+            case BUFFER_UNDERFLOW:
+              throw new SSLException("Buffer underflow in handshake and wrap");
+            case CLOSED:
+              try {
+                hsOutNetBuffer.flip();
+                while(hsOutNetBuffer.hasRemaining()) {
+                  socketChannel.write(hsOutNetBuffer);
+                }
+                hsInNetBuffer.clear();
+              } catch (Exception e) {
+                handshakeStatus = engine.getHandshakeStatus();
+              }
+              break;
+            default:
+              throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+          }
+          break;
+        case NEED_TASK:
+          Runnable task;
+          while((task = engine.getDelegatedTask()) != null) {
+            executor.execute(task);
+          }
+          handshakeStatus = engine.getHandshakeStatus();
+          break;
+        case FINISHED:
+          break;
+        case NOT_HANDSHAKING:
+          break;
+        default:
+          throw new IllegalStateException("Invalid SSL status: " + handshakeStatus);
+      }
+        }
+    return true;
+  }
+
+  public ByteBuffer enlargePacketBuffer(SSLEngine engine, ByteBuffer buffer) {
+    return enlargeBuffer(buffer, engine.getSession().getPacketBufferSize());
+  }
+
+  public ByteBuffer enlargeApplicationBuffer(SSLEngine engine, ByteBuffer buffer) {
+    return enlargeBuffer(buffer, engine.getSession().getApplicationBufferSize());
+  }
+
+  public ByteBuffer enlargeBuffer(ByteBuffer buffer, int bufferSize) {
+    if(bufferSize > buffer.capacity()) {
+      buffer = ByteBuffer.allocate(bufferSize);
+    }
+    else {
+      buffer = ByteBuffer.allocate(buffer.capacity() * 2);
+    }
+    return buffer;
+  }
+  public ByteBuffer handleBufferUnderflow(SSLEngine engine, ByteBuffer buffer) {
+    if(engine.getSession().getPacketBufferSize() < buffer.limit()) {
+      return buffer;
+    }
+    else {
+      ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer);
+      buffer.flip();
+      replaceBuffer.put(buffer);
+      return replaceBuffer;
+    }
+  }
+
+  public void read(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+    dataInNetBuffer.clear();
+    int bytesRead = socketChannel.read(dataInNetBuffer);
+    if(bytesRead > 0) {
+      dataInNetBuffer.flip();
+      while(dataInNetBuffer.hasRemaining()) {
+        dataInAppBuffer.clear();
+        SSLEngineResult result = engine.unwrap(dataInNetBuffer, dataInAppBuffer);
+        switch(result.getStatus()) {
+          case OK:
+            dataInAppBuffer.flip();
+            break;
+          case BUFFER_OVERFLOW:
+            dataInAppBuffer = enlargeApplicationBuffer(engine, dataInAppBuffer);
+            break;
+          case BUFFER_UNDERFLOW:
+            dataInNetBuffer = handleBufferUnderflow(engine, dataInNetBuffer);
+            break;
+          case CLOSED:
+            closeConnection(socketChannel, engine);
+            return;
+          default:
+            throw new IllegalStateException("invalid SSL status: " + result.getStatus());
+        }
+      }
+    }
+    else if(bytesRead < 0) {
+      handleEndOfStream(socketChannel, engine);
+    }
+  }
+
+  public void handleEndOfStream(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+    try {
+      engine.closeInbound();
+    }
+    catch (Exception e) {
+      Log.e("server-poc-test", "Close inbound forced");
+    }
+    closeConnection(socketChannel, engine);
+  }
+
+  public void closeConnection(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+    try{
+      serverKey.close();
+    } catch (IOException e){
+      e.printStackTrace();
+    }
+
+    try {
+      trustedCert.close();
+    } catch (IOException e){
+      e.printStackTrace();
+    }
+
+    engine.closeOutbound();
+    doHandshake(socketChannel, engine);
+    socketChannel.close();
+  }
+}