Merge "Enforce support for TEL URIs with self-managed CS."
diff --git a/apps/CtsVerifier/res/layout/voicemail_hide_in_call_settings.xml b/apps/CtsVerifier/res/layout/voicemail_hide_in_call_settings.xml
index eb8b81a..4d7803b 100644
--- a/apps/CtsVerifier/res/layout/voicemail_hide_in_call_settings.xml
+++ b/apps/CtsVerifier/res/layout/voicemail_hide_in_call_settings.xml
@@ -46,6 +46,12 @@
     </LinearLayout>
 
     <Button
+        android:id="@+id/call_settings_check_not_applicable"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/visual_voicemail_service_remove_sim_not_applicable"/>
+
+      <Button
         android:id="@+id/set_default_dialer"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/apps/CtsVerifier/res/layout/voicemail_hide_ringtone_settings.xml b/apps/CtsVerifier/res/layout/voicemail_hide_ringtone_settings.xml
index ea11314..9ccd909 100644
--- a/apps/CtsVerifier/res/layout/voicemail_hide_ringtone_settings.xml
+++ b/apps/CtsVerifier/res/layout/voicemail_hide_ringtone_settings.xml
@@ -32,11 +32,18 @@
         android:layout_height="wrap_content"
         android:text="@string/open_voicemail_settings_explanation"
         android:textSize="16dp"/>
+
     <Button
-        android:id="@+id/open_voicemail_settings"
+        android:id="@+id/voicemail_hide_ringtone_settings_not_applicable"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:text="@string/open_voicemail_settings"/>
+        android:text="@string/visual_voicemail_service_remove_sim_not_applicable"/>
+
+    <Button
+      android:id="@+id/open_voicemail_settings"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/open_voicemail_settings"/>
 
     <LinearLayout
         android:layout_width="wrap_content"
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
old mode 100644
new mode 100755
index 199a86f..ddfa283
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
@@ -388,7 +388,7 @@
                     Log.e(TAG, "failed to unpack data from mocklistener", e);
                 }
             }
-            pass &= found.size() == 3;
+            pass &= found.size() >= 3;
             status = pass ? PASS : FAIL;
         }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
old mode 100644
new mode 100755
index 0983580..269f4fd
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -271,9 +271,6 @@
                                 "data integrity test: notification ID (%d, %d)");
                         pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
                                 "data integrity test: notification when (%d, %d)");
-                    } else {
-                        pass = false;
-                        logFail("unexpected notification tag: " + tag);
                     }
                 } catch (JSONException e) {
                     pass = false;
@@ -281,7 +278,7 @@
                 }
             }
 
-            pass &= found.size() == 3;
+            pass &= found.size() >= 3;
             status = pass ? PASS : FAIL;
         }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/CallSettingsCheckActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/CallSettingsCheckActivity.java
index ac0f060..e2e4d30 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/CallSettingsCheckActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/CallSettingsCheckActivity.java
@@ -21,6 +21,8 @@
 import android.telecom.TelecomManager;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageView;
 
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
@@ -32,6 +34,10 @@
 
     private DefaultDialerChanger mDefaultDialerChanger;
 
+    private Button mSetDefaultDialerButton;
+    private Button mNotApplicableButton;
+    private ImageView mRestoreDefaultDialerImage;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -44,6 +50,22 @@
 
         mDefaultDialerChanger = new DefaultDialerChanger(this);
 
+        mSetDefaultDialerButton = findViewById(R.id.set_default_dialer);
+        mNotApplicableButton = findViewById(R.id.call_settings_check_not_applicable);
+        mRestoreDefaultDialerImage = findViewById(R.id.restore_default_dialer_image);
+
+        mNotApplicableButton.setOnClickListener(
+                new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        getPassButton().setEnabled(true);
+                        mSetDefaultDialerButton.setEnabled(false);
+
+                        mRestoreDefaultDialerImage.setImageDrawable(getDrawable(R.drawable.fs_warning));
+                    }
+                }
+        );
+
         findViewById(R.id.open_call_settings).setOnClickListener(
                 new OnClickListener() {
                     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/VoicemailSettingsCheckActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/VoicemailSettingsCheckActivity.java
index d4ac0db..f2a7345 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/VoicemailSettingsCheckActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/VoicemailSettingsCheckActivity.java
@@ -21,6 +21,8 @@
 import android.telephony.TelephonyManager;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageView;
 
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
@@ -30,6 +32,13 @@
  */
 public class VoicemailSettingsCheckActivity extends PassFailButtons.Activity {
 
+    private Button mNotApplicableButton;
+    private Button mOpenVoiceMailSettingsButton;
+    private Button mRingtoneSettingsDoesNotExistButton;
+    private Button mRingtoneSettingsExistsButton;
+
+    private ImageView mRestoreDefaultDialerImage;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -40,7 +49,30 @@
         setPassFailButtonClickListeners();
         getPassButton().setEnabled(false);
 
-        findViewById(R.id.open_voicemail_settings).setOnClickListener(
+
+        mNotApplicableButton = findViewById(R.id.voicemail_hide_ringtone_settings_not_applicable);
+        mOpenVoiceMailSettingsButton = findViewById(R.id.open_voicemail_settings);
+        mRingtoneSettingsDoesNotExistButton = findViewById(R.id.settings_hidden);
+        mRingtoneSettingsExistsButton = findViewById(R.id.settings_not_hidden);
+
+        mRestoreDefaultDialerImage = findViewById(R.id.restore_default_dialer_image);
+
+        mNotApplicableButton.setOnClickListener(
+                new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        getPassButton().setEnabled(true);
+
+                        mOpenVoiceMailSettingsButton.setEnabled(false);
+                        mRingtoneSettingsDoesNotExistButton.setEnabled(false);
+                        mRingtoneSettingsExistsButton.setEnabled(false);
+
+                        mRestoreDefaultDialerImage.setImageDrawable(getDrawable(R.drawable.fs_warning));
+                    }
+                }
+        );
+
+        mOpenVoiceMailSettingsButton.setOnClickListener(
                 new OnClickListener() {
                     @Override
                     public void onClick(View v) {
@@ -50,7 +82,7 @@
                 }
         );
 
-        findViewById(R.id.settings_hidden).setOnClickListener(
+        mRingtoneSettingsDoesNotExistButton.setOnClickListener(
                 new OnClickListener() {
                     @Override
                     public void onClick(View v) {
@@ -60,7 +92,7 @@
                 }
         );
 
-        findViewById(R.id.settings_not_hidden).setOnClickListener(
+        mRingtoneSettingsExistsButton.setOnClickListener(
                 new OnClickListener() {
                     @Override
                     public void onClick(View v) {
@@ -68,6 +100,5 @@
                     }
                 }
         );
-
     }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java
index a9369fa..f011f5e 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java
@@ -19,12 +19,14 @@
 import com.android.compatibility.common.tradefed.targetprep.PreconditionPreparer;
 import com.android.compatibility.common.tradefed.util.DynamicConfigFileReader;
 import com.android.ddmlib.IDevice;
+import com.android.ddmlib.Log;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.TargetSetupError;
@@ -110,6 +112,8 @@
     /* Key to retrieve resolution string in metrics upon MediaPreparerListener.testEnded() */
     private static final String RESOLUTION_STRING_KEY = "resolution";
 
+    private static final String LOG_TAG = "MediaPreparer";
+
     /*
      * In the case of MediaPreparer error, the default maximum resolution to push to the device.
      * Pushing higher resolutions may lead to insufficient storage for installing test APKs.
@@ -204,12 +208,22 @@
     }
 
     /*
-     * Copies the media files to the host from a predefined URL
-     * Updates mLocalMediaPath to be the pathname of the directory containing bbb_short and
-     * bbb_full media directories.
+     * Copies the media files to the host from a predefined URL.
+     *
+     * Synchronize this static method so that multiple shards won't download/extract
+     * this file to the same location on the host. Only an issue in Android O and above,
+     * where MediaPreparer is used for multiple, shardable modules.
      */
-    private void downloadMediaToHost(ITestDevice device, IBuildInfo buildInfo, File mediaFolder)
+    private static synchronized File downloadMediaToHost(ITestDevice device, IBuildInfo buildInfo)
             throws TargetSetupError {
+        // Retrieve default directory for storing media files
+        File mediaFolder = getDefaultMediaDir();
+        if (mediaFolder.exists() && mediaFolder.list().length > 0) {
+            // Folder has already been created and populated by previous MediaPreparer runs,
+            // assume all necessary media files exist inside.
+            return mediaFolder;
+        }
+        mediaFolder.mkdirs();
         URL url;
         try {
             // Get download URL from dynamic configuration service
@@ -222,12 +236,13 @@
         }
         File mediaFolderZip = new File(mediaFolder.getAbsolutePath() + ".zip");
         try {
-            logInfo("Downloading media files from %s", url.toString());
+            LogUtil.printLog(Log.LogLevel.INFO, LOG_TAG,
+                    String.format("Downloading media files from %s", url.toString()));
             URLConnection conn = url.openConnection();
             InputStream in = conn.getInputStream();
             mediaFolderZip.createNewFile();
             FileUtil.writeToFile(in, mediaFolderZip);
-            logInfo("Unzipping media files");
+            LogUtil.printLog(Log.LogLevel.INFO, LOG_TAG, "Unzipping media files");
             ZipUtil.extractZip(new ZipFile(mediaFolderZip), mediaFolder);
         } catch (IOException e) {
             FileUtil.recursiveDelete(mediaFolder);
@@ -237,6 +252,7 @@
         } finally {
             FileUtil.deleteFile(mediaFolderZip);
         }
+        return mediaFolder;
     }
 
     /*
@@ -321,20 +337,8 @@
         if (mLocalMediaPath == null) {
             // Option 'local-media-path' has not been defined
             // Get directory to store media files on this host
-            File mediaFolder = getDefaultMediaDir();
-            synchronized (MediaPreparer.class) {
-                // Synchronize this block so that multiple shards won't download/extract
-                // this file to the same location on the host. Only an issue in Android O and above,
-                // where MediaPreparer is used for multiple, shardable modules.
-                if(!mediaFolder.exists() || mediaFolder.list().length == 0){
-                    // If directory already exists and contains files, it has been created by
-                    // previous runs of MediaPreparer. Assume media files exist inside.
-                    // Else, create directory if needed and download/extract media files inside.
-                    mediaFolder.mkdirs();
-                    downloadMediaToHost(device, buildInfo, mediaFolder);
-                }
-            }
-            // set mLocalMediaPath to where the CTS media files have been extracted
+            File mediaFolder = downloadMediaToHost(device, buildInfo);
+            // set mLocalMediaPath to extraction location of media files
             updateLocalMediaPath(device, mediaFolder);
         }
         logInfo("Media files located on host at: %s", mLocalMediaPath);
diff --git a/hostsidetests/incident/apps/graphicsstatsapp/src/com/android/server/cts/device/graphicsstats/DrawFramesActivity.java b/hostsidetests/incident/apps/graphicsstatsapp/src/com/android/server/cts/device/graphicsstats/DrawFramesActivity.java
index 2b9e5a6..65fd3ff 100644
--- a/hostsidetests/incident/apps/graphicsstatsapp/src/com/android/server/cts/device/graphicsstats/DrawFramesActivity.java
+++ b/hostsidetests/incident/apps/graphicsstatsapp/src/com/android/server/cts/device/graphicsstats/DrawFramesActivity.java
@@ -114,7 +114,7 @@
     }
 
     private void jank() {
-        spinSleep(20);
+        spinSleep(24);
     }
 
     private void spinSleep(int durationMs) {
@@ -128,7 +128,7 @@
             jankIf(FRAME_JANK_ANIMATION);
         });
         if (isFrameFlagSet(FRAME_JANK_MISS_VSYNC)) {
-            spinSleep(32);
+            spinSleep(45);
         }
     }
 
@@ -161,7 +161,7 @@
         long timeoutDurationMs = 0;
         for (int frame : framesToDraw) {
             // 50ms base time + 20ms for every extra jank event
-            timeoutDurationMs += 50 + (20 * Integer.bitCount(frame));
+            timeoutDurationMs += 50 + (24 * Integer.bitCount(frame));
             if ((frame & FRAME_JANK_DAVEY_JR) != 0) {
                 timeoutDurationMs += 150;
             }
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index 1f0a567..7a9a534 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -147,8 +147,13 @@
         if (mDevice.doesFileExist("/system/etc/selinux/plat_file_contexts")) {
             devicePlatFcFile = getDeviceFile(mDevice, cachedDevicePlatFcFiles,
                     "/system/etc/selinux/plat_file_contexts", "plat_file_contexts");
-            deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
-                    "/vendor/etc/selinux/nonplat_file_contexts", "nonplat_file_contexts");
+            if (mDevice.doesFileExist("/vendor/etc/selinux/nonplat_file_contexts")){
+                deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
+                        "/vendor/etc/selinux/nonplat_file_contexts", "nonplat_file_contexts");
+            } else {
+                deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
+                        "/vendor/etc/selinux/vendor_file_contexts", "vendor_file_contexts");
+            }
         } else {
             devicePlatFcFile = getDeviceFile(mDevice, cachedDevicePlatFcFiles,
                     "/plat_file_contexts", "plat_file_contexts");
@@ -167,6 +172,9 @@
     private static File getDeviceFile(ITestDevice device,
             Map<ITestDevice, File> cache, String deviceFilePath,
             String tmpFileName) throws Exception {
+        if (!device.doesFileExist(deviceFilePath)){
+            throw new Exception();
+        }
         File file;
         synchronized (cache) {
             file = cache.get(device);
@@ -614,6 +622,7 @@
 
         /* run property_info_checker on property_contexts */
         ProcessBuilder pb = new ProcessBuilder(propertyInfoChecker.getAbsolutePath(),
+                devicePolicyFile.getAbsolutePath(),
                 devicePcFile.getAbsolutePath());
         pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
         pb.redirectErrorStream(true);
@@ -728,6 +737,24 @@
     }
 
     /**
+     * Tests that all types in /proc have the proc_type attribute.
+     *
+     * @throws Exception
+     */
+    public void testProcTypeViolators() throws Exception {
+        assertSepolicyTests("TestProcTypeViolations", "/sepolicy_tests");
+    }
+
+    /**
+     * Tests that all types in /sys have the sysfs_type attribute.
+     *
+     * @throws Exception
+     */
+    public void testSysfsTypeViolators() throws Exception {
+        assertSepolicyTests("TestSysfsTypeViolations", "/sepolicy_tests");
+    }
+
+    /**
      * Tests that all types on /vendor have the vendor_file_type attribute.
      *
      * @throws Exception
diff --git a/hostsidetests/security/src/android/security/cts/SecurityTestCase.java b/hostsidetests/security/src/android/security/cts/SecurityTestCase.java
index 277d591..b6d647a 100644
--- a/hostsidetests/security/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/security/src/android/security/cts/SecurityTestCase.java
@@ -35,10 +35,9 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        String cmdOut = getDevice().executeShellCommand("dumpsys meminfo");
-        long uptime = Long.parseLong(cmdOut.substring(cmdOut.indexOf("Uptime: ") + 8,
-                      cmdOut.indexOf("Realtime: ") - 1))/1000;
-        kernelStartTime = System.currentTimeMillis()/1000 - uptime;
+        String uptime = getDevice().executeShellCommand("cat /proc/uptime");
+        kernelStartTime = System.currentTimeMillis()/1000 -
+            Integer.parseInt(uptime.substring(0, uptime.indexOf('.')));
         //TODO:(badash@): Watch for other things to track.
         //     Specifically time when app framework starts
     }
@@ -86,11 +85,11 @@
     @Override
     public void tearDown() throws Exception {
         getDevice().waitForDeviceOnline(60 * 1000);
-        String cmdOut = getDevice().executeShellCommand("dumpsys meminfo");
-        long uptime = Long.parseLong(cmdOut.substring(cmdOut.indexOf("Uptime: ") + 8,
-                      cmdOut.indexOf("Realtime: ") - 1))/1000;
+        String uptime = getDevice().executeShellCommand("cat /proc/uptime");
         assertTrue("Phone has had a hard reset",
-            (System.currentTimeMillis()/1000 - uptime - kernelStartTime < 2));
+            (System.currentTimeMillis()/1000 -
+                Integer.parseInt(uptime.substring(0, uptime.indexOf('.')))
+                    - kernelStartTime < 2));
         //TODO(badash@): add ability to catch runtime restart
         getDevice().disableAdbRoot();
     }
diff --git a/hostsidetests/theme/assets/26/213dpi.zip b/hostsidetests/theme/assets/26/213dpi.zip
new file mode 100755
index 0000000..38531ba
--- /dev/null
+++ b/hostsidetests/theme/assets/26/213dpi.zip
Binary files differ
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
index 92a19a3..e0ff683 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
@@ -20,6 +20,7 @@
 
 import android.util.Log;
 
+import org.junit.AssumptionViolatedException;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -52,8 +53,14 @@
                 try {
                     base.evaluate();
                 } catch (Throwable t) {
-                    final String dump = runShellCommand("dumpsys autofill");
-                    Log.e(mTag, "dump for " + description.getDisplayName() + ": \n" + dump, t);
+                    if ((t instanceof AssumptionViolatedException)) {
+                        // This exception is used to indicate a test should be skipped and is
+                        // ignored by JUnit runners - we don't need to dump it...
+                        Log.w(mTag, "ignoring exception: " + t);
+                    } else {
+                        final String dump = runShellCommand("dumpsys autofill");
+                        Log.e(mTag, "dump for " + description.getDisplayName() + ": \n" + dump, t);
+                    }
                     throw t;
                 } finally {
                     if (!levelBefore.equals("verbose")) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
index 9130810..8d66e15 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
@@ -19,12 +19,15 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.service.autofill.CustomDescription;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
 import android.widget.RemoteViews;
 
 import org.junit.Ignore;
@@ -46,6 +49,7 @@
  */
 abstract class CustomDescriptionWithLinkTestCase extends AutoFillServiceTestCase {
 
+    private static final String TAG = "CustomDescriptionWithLinkTestCase";
     private static final String ID_LINK = "link";
 
     /**
@@ -64,8 +68,17 @@
      */
     @Test
     public final void testTapLink_changeOrientationThenTapBack() throws Exception {
+        final int width = sUiBot.getDevice().getDisplayWidth();
+        final int heigth = sUiBot.getDevice().getDisplayHeight();
+        final int min = Math.min(width, heigth);
+
+        assumeTrue("Screen size is too small (" + width + "x" + heigth + ")", min >= 500);
+        Log.d(TAG, "testTapLink_changeOrientationThenTapBack(): screen size is "
+                + width + "x" + heigth);
+
         sUiBot.setScreenOrientation(UiBot.PORTRAIT);
         try {
+            runShellCommand("wm size 1080x1920");
             runShellCommand("wm density 420");
             saveUiRestoredAfterTappingLinkTest(
                     PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
@@ -75,6 +88,7 @@
                 cleanUpAfterScreenOrientationIsBackToPortrait();
             } finally {
                 runShellCommand("wm density reset");
+                runShellCommand("wm size reset");
             }
         }
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index fffde3e..db81baf 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -99,6 +99,10 @@
         mAutoman = instrumentation.getUiAutomation();
     }
 
+    UiDevice getDevice() {
+        return mDevice;
+    }
+
     /**
      * Asserts the dataset chooser is not shown.
      */
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/ApnDatabaseTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/ApnDatabaseTest.java
index 372f22c..28afabc 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/ApnDatabaseTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/ApnDatabaseTest.java
@@ -17,23 +17,19 @@
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.provider.Telephony.Carriers;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
-import android.test.InstrumentationTestCase;
 import android.util.Log;
 
-import com.android.internal.telephony.uicc.IccUtils;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -53,6 +49,8 @@
     private static final String TAG = "ApnDatabaseTest";
 
     private ContentResolver mContentResolver;
+    private PackageManager mPackageManager;
+    private boolean mHasCellular;
 
     private static final String NAME = "carrierName";
     private static final String APN = "apn";
@@ -99,6 +97,12 @@
     @Before
     public void setUp() throws Exception {
         mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
+        mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
+        // Checks whether the cellular stack should be running on this device.
+        mHasCellular = mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+        if (!mHasCellular) {
+            Log.e(TAG, "No cellular support, all tests will be skipped.");
+        }
     }
 
     private void failMessage() {
@@ -112,6 +116,7 @@
      */
     @Test
     public void testValidCase() {
+        if (!mHasCellular) return;
         Uri uri = Carriers.CONTENT_URI;
         // CONTENT_URI = Uri.parse("content://telephony/carriers");
         // Create A set of column_name/value pairs to add to the database.
@@ -179,6 +184,7 @@
 
     @Test
     public void testQueryConflictCase() {
+        if (!mHasCellular) return;
         String invalidColumn = "random";
         Uri uri = Carriers.CONTENT_URI;
         // CONTENT_URI = Uri.parse("content://telephony/carriers");
@@ -234,6 +240,7 @@
 
     @Test
     public void testUpdateConflictCase() {
+        if (!mHasCellular) return;
         Uri uri = Carriers.CONTENT_URI;
         // CONTENT_URI = Uri.parse("content://telephony/carriers");
         // Create A set of column_name/value pairs to add to the database.
@@ -300,6 +307,7 @@
 
     @Test
     public void testDeleteConflictCase() {
+        if (!mHasCellular) return;
         String invalidColumn = "random";
         Uri uri = Carriers.CONTENT_URI;
         // CONTENT_URI = Uri.parse("content://telephony/carriers");
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
index 63f8347..353b04a 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
@@ -16,6 +16,7 @@
 package android.carrierapi.cts;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -52,6 +53,7 @@
 @RunWith(AndroidJUnit4.class)
 public class NetworkScanApiTest {
     private TelephonyManager mTelephonyManager;
+    private PackageManager mPackageManager;
     private static final String TAG = "NetworkScanApiTest";
     private int mNetworkScanStatus;
     private static final int EVENT_NETWORK_SCAN_START = 100;
@@ -72,6 +74,7 @@
     public void setUp() throws Exception {
         mTelephonyManager = (TelephonyManager)
                 InstrumentationRegistry.getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
         mTestHandlerThread = new NetworkScanHandlerThread(TAG);
         mTestHandlerThread.start();
     }
@@ -179,6 +182,11 @@
      */
     @Test
     public void testRequestNetworkScan() throws InterruptedException {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            // Checks whether the cellular stack should be running on this device.
+            Log.e(TAG, "No cellular support, the test will be skipped.");
+            return;
+        }
         if (!mTelephonyManager.hasCarrierPrivileges()) {
             fail("This test requires a SIM card with carrier privilege rule on it.");
         }
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index dbebbc0..3a016bc 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -331,8 +331,12 @@
     public void testManageStorage() {
         assertCanBeHandled(new Intent(StorageManager.ACTION_MANAGE_STORAGE));
     }
- 
+
     public void testVoiceCommand() {
+        if (FeatureUtil.isLowRam()) {
+            // Low ram devices do not support voice command, skip this test
+            return;
+        }
         PackageManager packageManager = mContext.getPackageManager();
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
             Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
index fd1ed3e..b1c1f03 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
@@ -498,8 +498,8 @@
         final Theme t = res.newTheme();
         halfDrawable.applyTheme(t);
         float approxDouble = 1 / approxHalf;
-        // Reproduce imprecise truncated scale down, and back up. Note that we don't round.
-        assertEquals((int)(approxDouble * ((int)(origInsetHoriz * approxHalf))),
+        // Reproduce imprecise truncated scale down, and back up.
+        assertEquals(Math.round(approxDouble * Math.round(origInsetHoriz * approxHalf)),
                 halfDrawable.getIntrinsicWidth() - halfDrawable.getDrawable().getIntrinsicWidth());
         doubleDrawable.applyTheme(t);
         assertEquals(origInsetHoriz, doubleDrawable.getIntrinsicWidth()
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index aab6b26..549f3d5 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -57,6 +57,8 @@
 
 LOCAL_JNI_SHARED_LIBRARIES := \
     libaudio_jni \
+    libc++ \
+    libctscodecutils_jni \
     libctsimagereader_jni \
     libctsmediadrm_jni \
     libctsmediacodec_jni \
diff --git a/tests/tests/media/libaudiojni/Android.mk b/tests/tests/media/libaudiojni/Android.mk
index d1b1ced..af9d989 100644
--- a/tests/tests/media/libaudiojni/Android.mk
+++ b/tests/tests/media/libaudiojni/Android.mk
@@ -35,7 +35,7 @@
 	$(call include-path-for, wilhelm)
 
 LOCAL_SHARED_LIBRARIES := libandroid liblog libnativehelper_compat_libc++ libOpenSLES
-LOCAL_CXX_STL := libc++_static
+LOCAL_CXX_STL := libc++
 
 LOCAL_CFLAGS := -Werror -Wall
 
diff --git a/tests/tests/media/libimagereaderjni/Android.mk b/tests/tests/media/libimagereaderjni/Android.mk
index 70699a7..6ce591c 100644
--- a/tests/tests/media/libimagereaderjni/Android.mk
+++ b/tests/tests/media/libimagereaderjni/Android.mk
@@ -31,8 +31,7 @@
     libnativewindow \
     liblog
 
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_CXX_STL := libc++
 
 LOCAL_CFLAGS := -Werror -Wall
 
diff --git a/tests/tests/media/libmediandkjni/Android.mk b/tests/tests/media/libmediandkjni/Android.mk
index 287fc3e..e0af7dc 100644
--- a/tests/tests/media/libmediandkjni/Android.mk
+++ b/tests/tests/media/libmediandkjni/Android.mk
@@ -15,6 +15,37 @@
 LOCAL_PATH := $(call my-dir)
 
 #------------------------------------------------------------------------------
+# Builds libctscodecutils_jni.so
+#
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libctscodecutils_jni
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+  codec-utils-jni.cpp \
+  md5_utils.cpp
+
+LOCAL_C_INCLUDES := \
+  $(JNI_H_INCLUDE) \
+  system/core/include
+
+LOCAL_C_INCLUDES += $(call include-path-for, mediandk)
+
+LOCAL_SHARED_LIBRARIES := \
+  libnativehelper_compat_libc++ \
+  liblog
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_NDK_STL_VARIANT := system
+
+LOCAL_CFLAGS := -Werror -Wall -DEGL_EGLEXT_PROTOTYPES -std=gnu++14
+
+include $(BUILD_SHARED_LIBRARY)
+
+#------------------------------------------------------------------------------
 # Builds libctsmediacodec_jni.so
 #
 include $(CLEAR_VARS)
@@ -25,8 +56,6 @@
 
 LOCAL_SRC_FILES := \
   native-media-jni.cpp \
-  codec-utils-jni.cpp  \
-  md5_utils.cpp \
   native_media_utils.cpp \
   native_media_decoder_source.cpp \
   native_media_encoder_jni.cpp
@@ -40,14 +69,14 @@
 LOCAL_SHARED_LIBRARIES := \
   libandroid libnativehelper_compat_libc++ \
   liblog libmediandk libEGL
-LOCAL_NDK_STL_VARIANT := c++_static
 
-LOCAL_SDK_VERSION := current
+LOCAL_CXX_STL := libc++
 
 LOCAL_CFLAGS := -Werror -Wall -DEGL_EGLEXT_PROTOTYPES -std=gnu++14
 
 include $(BUILD_SHARED_LIBRARY)
 
+
 #------------------------------------------------------------------------------
 # Builds libctsmediadrm_jni.so
 #
@@ -75,10 +104,8 @@
   libandroid libnativehelper_compat_libc++ \
   liblog libmediandk libdl libEGL
 
-LOCAL_SDK_VERSION := current
-
 LOCAL_CFLAGS := -Werror -Wall -DEGL_EGLEXT_PROTOTYPES
 
-LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_CXX_STL := libc++
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/media/libmediandkjni/codec-utils-jni.cpp b/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
index d7bd74e..5525e4e 100644
--- a/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
+++ b/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
@@ -24,6 +24,13 @@
 #include <sys/types.h>
 #include <jni.h>
 
+// workaround for using ScopedLocalRef with system runtime
+// TODO: Remove this after b/74632104 is fixed
+namespace std
+{
+  typedef decltype(nullptr) nullptr_t;
+}
+
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedLocalRef.h>
 
diff --git a/tests/tests/media/libndkaudio/Android.mk b/tests/tests/media/libndkaudio/Android.mk
index 29e2118..16e490e 100644
--- a/tests/tests/media/libndkaudio/Android.mk
+++ b/tests/tests/media/libndkaudio/Android.mk
@@ -37,7 +37,7 @@
   AudioRecorder.cpp \
   com_android_ndkaudio_AudioRecorder.cpp
 
-LOCAL_CXX_STL := libc++_static
+LOCAL_CXX_STL := libc++
 
 LOCAL_SHARED_LIBRARIES := liblog libOpenSLES
 
diff --git a/tests/tests/media/src/android/media/cts/CodecUtils.java b/tests/tests/media/src/android/media/cts/CodecUtils.java
index 24c1174..ae785b2 100644
--- a/tests/tests/media/src/android/media/cts/CodecUtils.java
+++ b/tests/tests/media/src/android/media/cts/CodecUtils.java
@@ -38,7 +38,7 @@
     /** Load jni on initialization */
     static {
         Log.i(TAG, "before loadlibrary");
-        System.loadLibrary("ctsmediacodec_jni");
+        System.loadLibrary("ctscodecutils_jni");
         Log.i(TAG, "after loadlibrary");
     }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index c453f4d..58d803e 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -342,6 +342,12 @@
      */
     public void testGetScaledFrameAtTime() {
         int resId = R.raw.binary_counter_320x240_30fps_600frames;
+        if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")
+            && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+            MediaUtils.skipTest("no video codecs for resource on watch");
+            return;
+        }
+
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         Resources resources = getContext().getResources();
         AssetFileDescriptor afd = resources.openRawResourceFd(resId);
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index c885942..9de47bf 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -18,11 +18,16 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
+import android.app.Instrumentation;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -38,12 +43,17 @@
 import android.net.NetworkRequest;
 import android.net.wifi.WifiManager;
 import android.os.Looper;
+import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
 import android.system.Os;
 import android.system.OsConstants;
 import android.test.AndroidTestCase;
+import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.R;
 import com.android.internal.telephony.PhoneConstants;
 
 import java.io.File;
@@ -51,6 +61,7 @@
 import java.io.InputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.lang.NumberFormatException;
 import java.net.Socket;
 import java.net.InetSocketAddress;
 import java.util.HashMap;
@@ -58,6 +69,8 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class ConnectivityManagerTest extends AndroidTestCase {
 
@@ -72,6 +85,9 @@
     private static final String TEST_HOST = "connectivitycheck.gstatic.com";
     private static final int SOCKET_TIMEOUT_MS = 2000;
     private static final int SEND_BROADCAST_TIMEOUT = 30000;
+    private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 5000;
+    private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
+    private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
     private static final int HTTP_PORT = 80;
     private static final String HTTP_REQUEST =
             "GET /generate_204 HTTP/1.0\r\n" +
@@ -101,6 +117,7 @@
     private static final int MIN_NUM_NETWORK_TYPES = 1;
 
     private Context mContext;
+    private Instrumentation mInstrumentation;
     private ConnectivityManager mCm;
     private WifiManager mWifiManager;
     private PackageManager mPackageManager;
@@ -113,6 +130,7 @@
         super.setUp();
         Looper.prepare();
         mContext = getContext();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
         mPackageManager = mContext.getPackageManager();
@@ -835,4 +853,146 @@
         assertTrue(lowerBoundSec <= interval);
         assertTrue(interval <= upperBoundSec);
     }
+
+    // Returns "true", "false" or "none"
+    private String getWifiMeteredStatus(String ssid) throws Exception {
+        // Interestingly giving the SSID as an argument to list wifi-networks
+        // only works iff the network in question has the "false" policy.
+        // Also unfortunately runShellCommand does not pass the command to the interpreter
+        // so it's not possible to | grep the ssid.
+        final String command = "cmd netpolicy list wifi-networks";
+        final String policyString = runShellCommand(mInstrumentation, command);
+
+        final Matcher m = Pattern.compile("^" + ssid + ";(true|false|none)$",
+                Pattern.MULTILINE | Pattern.UNIX_LINES).matcher(policyString);
+        if (!m.find()) {
+            fail("Unexpected format from cmd netpolicy");
+        }
+        return m.group(1);
+    }
+
+    // metered should be "true", "false" or "none"
+    private void setWifiMeteredStatus(String ssid, String metered) throws Exception {
+        final String setCommand = "cmd netpolicy set metered-network " + ssid + " " + metered;
+        runShellCommand(mInstrumentation, setCommand);
+        assertEquals(getWifiMeteredStatus(ssid), metered);
+    }
+
+    private String unquoteSSID(String ssid) {
+        // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
+        // Otherwise it's guaranteed not to start with a quote.
+        if (ssid.charAt(0) == '"') {
+            return ssid.substring(1, ssid.length() - 1);
+        } else {
+            return ssid;
+        }
+    }
+
+    private void waitForActiveNetworkMetered(boolean requestedMeteredness) throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final NetworkCallback networkCallback = new NetworkCallback() {
+            @Override
+            public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+                final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
+                if (metered == requestedMeteredness) {
+                    latch.countDown();
+                }
+            }
+        };
+        // Registering a callback here guarantees onCapabilitiesChanged is called immediately
+        // with the current setting. Therefore, if the setting has already been changed,
+        // this method will return right away, and if not it will wait for the setting to change.
+        mCm.registerDefaultNetworkCallback(networkCallback);
+        if (!latch.await(NETWORK_CHANGE_METEREDNESS_TIMEOUT, TimeUnit.MILLISECONDS)) {
+            fail("Timed out waiting for active network metered status to change to "
+                 + requestedMeteredness + " ; network = " + mCm.getActiveNetwork());
+        }
+        mCm.unregisterNetworkCallback(networkCallback);
+    }
+
+    private void assertMultipathPreferenceIsEventually(Network network, int oldValue,
+            int expectedValue) {
+        // Sanity check : if oldValue == expectedValue, there is no way to guarantee the test
+        // is not flaky.
+        assertNotSame(oldValue, expectedValue);
+
+        for (int i = 0; i < NUM_TRIES_MULTIPATH_PREF_CHECK; ++i) {
+            final int actualValue = mCm.getMultipathPreference(network);
+            if (actualValue == expectedValue) {
+                return;
+            }
+            if (actualValue != oldValue) {
+                fail("Multipath preference is neither previous (" + oldValue
+                        + ") nor expected (" + expectedValue + ")");
+            }
+            SystemClock.sleep(INTERVAL_MULTIPATH_PREF_CHECK_MS);
+        }
+        fail("Timed out waiting for multipath preference to change. expected = "
+                + expectedValue + " ; actual = " + mCm.getMultipathPreference(network));
+    }
+
+    private int getCurrentMeteredMultipathPreference(ContentResolver resolver) {
+        final String rawMeteredPref = Settings.Global.getString(resolver,
+                NETWORK_METERED_MULTIPATH_PREFERENCE);
+        return TextUtils.isEmpty(rawMeteredPref)
+            ? mContext.getResources().getInteger(R.integer.config_networkMeteredMultipathPreference)
+            : Integer.parseInt(rawMeteredPref);
+    }
+
+    private int findNextPrefValue(ContentResolver resolver) {
+        // A bit of a nuclear hammer, but race conditions in CTS are bad. To be able to
+        // detect a correct setting value without race conditions, the next pref must
+        // be a valid value (range 0..3) that is different from the old setting of the
+        // metered preference and from the unmetered preference.
+        final int meteredPref = getCurrentMeteredMultipathPreference(resolver);
+        final int unmeteredPref = ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED;
+        if (0 != meteredPref && 0 != unmeteredPref) return 0;
+        if (1 != meteredPref && 1 != unmeteredPref) return 1;
+        return 2;
+    }
+
+    /**
+     * Verify that getMultipathPreference does return appropriate values
+     * for metered and unmetered networks.
+     */
+    public void testGetMultipathPreference() throws Exception {
+        final ContentResolver resolver = mContext.getContentResolver();
+        final Network network = ensureWifiConnected();
+        final String ssid = unquoteSSID(mWifiManager.getConnectionInfo().getSSID());
+        final String oldMeteredSetting = getWifiMeteredStatus(ssid);
+        final String oldMeteredMultipathPreference = Settings.Global.getString(
+                resolver, NETWORK_METERED_MULTIPATH_PREFERENCE);
+        try {
+            final int initialMeteredPreference = getCurrentMeteredMultipathPreference(resolver);
+            int newMeteredPreference = findNextPrefValue(resolver);
+            Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
+                    Integer.toString(newMeteredPreference));
+            setWifiMeteredStatus(ssid, "true");
+            waitForActiveNetworkMetered(true);
+            assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
+                    NET_CAPABILITY_NOT_METERED), false);
+            assertMultipathPreferenceIsEventually(network, initialMeteredPreference,
+                    newMeteredPreference);
+
+            final int oldMeteredPreference = newMeteredPreference;
+            newMeteredPreference = findNextPrefValue(resolver);
+            Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
+                    Integer.toString(newMeteredPreference));
+            assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
+                    NET_CAPABILITY_NOT_METERED), false);
+            assertMultipathPreferenceIsEventually(network,
+                    oldMeteredPreference, newMeteredPreference);
+
+            setWifiMeteredStatus(ssid, "false");
+            waitForActiveNetworkMetered(false);
+            assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
+                    NET_CAPABILITY_NOT_METERED), true);
+            assertMultipathPreferenceIsEventually(network, newMeteredPreference,
+                    ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED);
+        } finally {
+            Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
+                    oldMeteredMultipathPreference);
+            setWifiMeteredStatus(ssid, oldMeteredSetting);
+        }
+    }
 }
diff --git a/tests/tests/net/src/android/net/cts/IpSecBaseTest.java b/tests/tests/net/src/android/net/cts/IpSecBaseTest.java
new file mode 100644
index 0000000..7132ecf
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/IpSecBaseTest.java
@@ -0,0 +1,518 @@
+/*
+ * 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.net.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.content.Context;
+import android.net.IpSecAlgorithm;
+import android.net.IpSecManager;
+import android.net.IpSecTransform;
+import android.system.Os;
+import android.system.OsConstants;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class IpSecBaseTest extends AndroidTestCase {
+
+    private static final String TAG = IpSecBaseTest.class.getSimpleName();
+
+    protected static final String IPV4_LOOPBACK = "127.0.0.1";
+    protected static final String IPV6_LOOPBACK = "::1";
+    protected static final String[] LOOPBACK_ADDRS = new String[] {IPV4_LOOPBACK, IPV6_LOOPBACK};
+    protected static final int[] DIRECTIONS =
+            new int[] {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT};
+
+    protected static final byte[] TEST_DATA = "Best test data ever!".getBytes();
+    protected static final int DATA_BUFFER_LEN = 4096;
+    protected static final int SOCK_TIMEOUT = 500;
+
+    private static final byte[] KEY_DATA = {
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+        0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+        0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+        0x20, 0x21, 0x22, 0x23
+    };
+
+    protected static final byte[] AUTH_KEY = getKey(256);
+    protected static final byte[] CRYPT_KEY = getKey(256);
+
+    protected IpSecManager mISM;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        mISM = (IpSecManager) getContext().getSystemService(Context.IPSEC_SERVICE);
+    }
+
+    protected static byte[] getKey(int bitLength) {
+        return Arrays.copyOf(KEY_DATA, bitLength / 8);
+    }
+
+    protected static int getDomain(InetAddress address) {
+        int domain;
+        if (address instanceof Inet6Address) {
+            domain = OsConstants.AF_INET6;
+        } else {
+            domain = OsConstants.AF_INET;
+        }
+        return domain;
+    }
+
+    protected static int getPort(FileDescriptor sock) throws Exception {
+        return ((InetSocketAddress) Os.getsockname(sock)).getPort();
+    }
+
+    public static interface GenericSocket extends AutoCloseable {
+        void send(byte[] data) throws Exception;
+
+        byte[] receive() throws Exception;
+
+        int getPort() throws Exception;
+
+        void close() throws Exception;
+
+        void applyTransportModeTransform(
+                IpSecManager ism, int direction, IpSecTransform transform) throws Exception;
+
+        void removeTransportModeTransforms(IpSecManager ism) throws Exception;
+    }
+
+    public static interface GenericTcpSocket extends GenericSocket {}
+
+    public static interface GenericUdpSocket extends GenericSocket {
+        void sendTo(byte[] data, InetAddress dstAddr, int port) throws Exception;
+    }
+
+    public abstract static class NativeSocket implements GenericSocket {
+        public FileDescriptor mFd;
+
+        public NativeSocket(FileDescriptor fd) {
+            mFd = fd;
+        }
+
+        @Override
+        public void send(byte[] data) throws Exception {
+            Os.write(mFd, data, 0, data.length);
+        }
+
+        @Override
+        public byte[] receive() throws Exception {
+            byte[] in = new byte[DATA_BUFFER_LEN];
+            AtomicInteger bytesRead = new AtomicInteger(-1);
+
+            Thread readSockThread = new Thread(() -> {
+                long startTime = System.currentTimeMillis();
+                while (bytesRead.get() < 0 && System.currentTimeMillis() < startTime + SOCK_TIMEOUT) {
+                    try {
+                        bytesRead.set(Os.recvfrom(mFd, in, 0, DATA_BUFFER_LEN, 0, null));
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error encountered reading from socket", e);
+                    }
+                }
+            });
+
+            readSockThread.start();
+            readSockThread.join(SOCK_TIMEOUT);
+
+            if (bytesRead.get() < 0) {
+                throw new IOException("No data received from socket");
+            }
+
+            return Arrays.copyOfRange(in, 0, bytesRead.get());
+        }
+
+        @Override
+        public int getPort() throws Exception {
+            return IpSecBaseTest.getPort(mFd);
+        }
+
+        @Override
+        public void close() throws Exception {
+            Os.close(mFd);
+        }
+
+        @Override
+        public void applyTransportModeTransform(
+                IpSecManager ism, int direction, IpSecTransform transform) throws Exception {
+            ism.applyTransportModeTransform(mFd, direction, transform);
+        }
+
+        @Override
+        public void removeTransportModeTransforms(IpSecManager ism) throws Exception {
+            ism.removeTransportModeTransforms(mFd);
+        }
+    }
+
+    public static class NativeTcpSocket extends NativeSocket implements GenericTcpSocket {
+        public NativeTcpSocket(FileDescriptor fd) {
+            super(fd);
+        }
+    }
+
+    public static class NativeUdpSocket extends NativeSocket implements GenericUdpSocket {
+        public NativeUdpSocket(FileDescriptor fd) {
+            super(fd);
+        }
+
+        @Override
+        public void sendTo(byte[] data, InetAddress dstAddr, int port) throws Exception {
+            Os.sendto(mFd, data, 0, data.length, 0, dstAddr, port);
+        }
+    }
+
+    public static class JavaUdpSocket implements GenericUdpSocket {
+        public final DatagramSocket mSocket;
+
+        public JavaUdpSocket(InetAddress localAddr) {
+            try {
+                mSocket = new DatagramSocket(0, localAddr);
+                mSocket.setSoTimeout(SOCK_TIMEOUT);
+            } catch (SocketException e) {
+                // Fail loudly if we can't set up sockets properly. And without the timeout, we
+                // could easily end up in an endless wait.
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void send(byte[] data) throws Exception {
+            mSocket.send(new DatagramPacket(data, data.length));
+        }
+
+        @Override
+        public void sendTo(byte[] data, InetAddress dstAddr, int port) throws Exception {
+            mSocket.send(new DatagramPacket(data, data.length, dstAddr, port));
+        }
+
+        @Override
+        public int getPort() throws Exception {
+            return mSocket.getLocalPort();
+        }
+
+        @Override
+        public void close() throws Exception {
+            mSocket.close();
+        }
+
+        @Override
+        public byte[] receive() throws Exception {
+            DatagramPacket data = new DatagramPacket(new byte[DATA_BUFFER_LEN], DATA_BUFFER_LEN);
+            mSocket.receive(data);
+            return Arrays.copyOfRange(data.getData(), 0, data.getLength());
+        }
+
+        @Override
+        public void applyTransportModeTransform(
+                IpSecManager ism, int direction, IpSecTransform transform) throws Exception {
+            ism.applyTransportModeTransform(mSocket, direction, transform);
+        }
+
+        @Override
+        public void removeTransportModeTransforms(IpSecManager ism) throws Exception {
+            ism.removeTransportModeTransforms(mSocket);
+        }
+    }
+
+    public static class JavaTcpSocket implements GenericTcpSocket {
+        public final Socket mSocket;
+
+        public JavaTcpSocket(Socket socket) {
+            mSocket = socket;
+            try {
+                mSocket.setSoTimeout(SOCK_TIMEOUT);
+            } catch (SocketException e) {
+                // Fail loudly if we can't set up sockets properly. And without the timeout, we
+                // could easily end up in an endless wait.
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void send(byte[] data) throws Exception {
+            mSocket.getOutputStream().write(data);
+        }
+
+        @Override
+        public byte[] receive() throws Exception {
+            byte[] in = new byte[DATA_BUFFER_LEN];
+            int bytesRead = mSocket.getInputStream().read(in);
+            return Arrays.copyOfRange(in, 0, bytesRead);
+        }
+
+        @Override
+        public int getPort() throws Exception {
+            return mSocket.getLocalPort();
+        }
+
+        @Override
+        public void close() throws Exception {
+            mSocket.close();
+        }
+
+        @Override
+        public void applyTransportModeTransform(
+                IpSecManager ism, int direction, IpSecTransform transform) throws Exception {
+            ism.applyTransportModeTransform(mSocket, direction, transform);
+        }
+
+        @Override
+        public void removeTransportModeTransforms(IpSecManager ism) throws Exception {
+            ism.removeTransportModeTransforms(mSocket);
+        }
+    }
+
+    public static class SocketPair<T> {
+        public final T mLeftSock;
+        public final T mRightSock;
+
+        public SocketPair(T leftSock, T rightSock) {
+            mLeftSock = leftSock;
+            mRightSock = rightSock;
+        }
+    }
+
+    protected static void applyTransformBidirectionally(
+            IpSecManager ism, IpSecTransform transform, GenericSocket socket) throws Exception {
+        for (int direction : DIRECTIONS) {
+            socket.applyTransportModeTransform(ism, direction, transform);
+        }
+    }
+
+    public static SocketPair<NativeUdpSocket> getNativeUdpSocketPair(
+            InetAddress localAddr, IpSecManager ism, IpSecTransform transform, boolean connected)
+            throws Exception {
+        int domain = getDomain(localAddr);
+
+        NativeUdpSocket leftSock = new NativeUdpSocket(
+            Os.socket(domain, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP));
+        NativeUdpSocket rightSock = new NativeUdpSocket(
+            Os.socket(domain, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP));
+
+        for (NativeUdpSocket sock : new NativeUdpSocket[] {leftSock, rightSock}) {
+            applyTransformBidirectionally(ism, transform, sock);
+            Os.bind(sock.mFd, localAddr, 0);
+        }
+
+        if (connected) {
+            Os.connect(leftSock.mFd, localAddr, rightSock.getPort());
+            Os.connect(rightSock.mFd, localAddr, leftSock.getPort());
+        }
+
+        return new SocketPair<>(leftSock, rightSock);
+    }
+
+    public static SocketPair<NativeTcpSocket> getNativeTcpSocketPair(
+            InetAddress localAddr, IpSecManager ism, IpSecTransform transform) throws Exception {
+        int domain = getDomain(localAddr);
+
+        NativeTcpSocket server = new NativeTcpSocket(
+                Os.socket(domain, OsConstants.SOCK_STREAM, OsConstants.IPPROTO_TCP));
+        NativeTcpSocket client = new NativeTcpSocket(
+                Os.socket(domain, OsConstants.SOCK_STREAM, OsConstants.IPPROTO_TCP));
+
+        Os.bind(server.mFd, localAddr, 0);
+
+        applyTransformBidirectionally(ism, transform, server);
+        applyTransformBidirectionally(ism, transform, client);
+
+        Os.listen(server.mFd, 10);
+        Os.connect(client.mFd, localAddr, server.getPort());
+        NativeTcpSocket accepted = new NativeTcpSocket(Os.accept(server.mFd, null));
+
+        applyTransformBidirectionally(ism, transform, accepted);
+        server.close();
+
+        return new SocketPair<>(client, accepted);
+    }
+
+    public static SocketPair<JavaUdpSocket> getJavaUdpSocketPair(
+            InetAddress localAddr, IpSecManager ism, IpSecTransform transform, boolean connected)
+            throws Exception {
+        JavaUdpSocket leftSock = new JavaUdpSocket(localAddr);
+        JavaUdpSocket rightSock = new JavaUdpSocket(localAddr);
+
+        applyTransformBidirectionally(ism, transform, leftSock);
+        applyTransformBidirectionally(ism, transform, rightSock);
+
+        if (connected) {
+            leftSock.mSocket.connect(localAddr, rightSock.mSocket.getLocalPort());
+            rightSock.mSocket.connect(localAddr, leftSock.mSocket.getLocalPort());
+        }
+
+        return new SocketPair<>(leftSock, rightSock);
+    }
+
+    public static SocketPair<JavaTcpSocket> getJavaTcpSocketPair(
+            InetAddress localAddr, IpSecManager ism, IpSecTransform transform) throws Exception {
+        JavaTcpSocket clientSock = new JavaTcpSocket(new Socket());
+        ServerSocket serverSocket = new ServerSocket();
+        serverSocket.bind(new InetSocketAddress(localAddr, 0));
+
+        // While technically the client socket does not need to be bound, the OpenJDK implementation
+        // of Socket only allocates an FD when bind() or connect() or other similar methods are
+        // called. So we call bind to force the FD creation, so that we can apply a transform to it
+        // prior to socket connect.
+        clientSock.mSocket.bind(new InetSocketAddress(localAddr, 0));
+
+        // IpSecService doesn't support serverSockets at the moment; workaround using FD
+        FileDescriptor serverFd = serverSocket.getImpl().getFD$();
+
+        applyTransformBidirectionally(ism, transform, new NativeTcpSocket(serverFd));
+        applyTransformBidirectionally(ism, transform, clientSock);
+
+        clientSock.mSocket.connect(new InetSocketAddress(localAddr, serverSocket.getLocalPort()));
+        JavaTcpSocket acceptedSock = new JavaTcpSocket(serverSocket.accept());
+
+        applyTransformBidirectionally(ism, transform, acceptedSock);
+        serverSocket.close();
+
+        return new SocketPair<>(clientSock, acceptedSock);
+    }
+
+    private void checkSocketPair(GenericSocket left, GenericSocket right) throws Exception {
+        left.send(TEST_DATA);
+        assertArrayEquals(TEST_DATA, right.receive());
+
+        right.send(TEST_DATA);
+        assertArrayEquals(TEST_DATA, left.receive());
+
+        left.close();
+        right.close();
+    }
+
+    private void checkUnconnectedUdpSocketPair(
+            GenericUdpSocket left, GenericUdpSocket right, InetAddress localAddr) throws Exception {
+        left.sendTo(TEST_DATA, localAddr, right.getPort());
+        assertArrayEquals(TEST_DATA, right.receive());
+
+        right.sendTo(TEST_DATA, localAddr, left.getPort());
+        assertArrayEquals(TEST_DATA, left.receive());
+
+        left.close();
+        right.close();
+    }
+
+    protected static IpSecTransform buildIpSecTransform(
+            Context mContext,
+            IpSecManager.SecurityParameterIndex spi,
+            IpSecManager.UdpEncapsulationSocket encapSocket,
+            InetAddress remoteAddr)
+            throws Exception {
+        String localAddr = (remoteAddr instanceof Inet4Address) ? IPV4_LOOPBACK : IPV6_LOOPBACK;
+        IpSecTransform.Builder builder =
+                new IpSecTransform.Builder(mContext)
+                .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
+                .setAuthentication(
+                        new IpSecAlgorithm(
+                                IpSecAlgorithm.AUTH_HMAC_SHA256,
+                                AUTH_KEY,
+                                AUTH_KEY.length * 4));
+
+        if (encapSocket != null) {
+            builder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
+        }
+
+        return builder.buildTransportModeTransform(InetAddress.getByName(localAddr), spi);
+    }
+
+    private IpSecTransform buildDefaultTransform(InetAddress localAddr) throws Exception {
+        try (IpSecManager.SecurityParameterIndex spi =
+                mISM.allocateSecurityParameterIndex(localAddr)) {
+            return buildIpSecTransform(mContext, spi, null, localAddr);
+        }
+    }
+
+    public void testJavaTcpSocketPair() throws Exception {
+        for (String addr : LOOPBACK_ADDRS) {
+            InetAddress local = InetAddress.getByName(addr);
+            try (IpSecTransform transform = buildDefaultTransform(local)) {
+                SocketPair<JavaTcpSocket> sockets = getJavaTcpSocketPair(local, mISM, transform);
+                checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
+            }
+        }
+    }
+
+    public void testJavaUdpSocketPair() throws Exception {
+        for (String addr : LOOPBACK_ADDRS) {
+            InetAddress local = InetAddress.getByName(addr);
+            try (IpSecTransform transform = buildDefaultTransform(local)) {
+                SocketPair<JavaUdpSocket> sockets =
+                        getJavaUdpSocketPair(local, mISM, transform, true);
+                checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
+            }
+        }
+    }
+
+    public void testJavaUdpSocketPairUnconnected() throws Exception {
+        for (String addr : LOOPBACK_ADDRS) {
+            InetAddress local = InetAddress.getByName(addr);
+            try (IpSecTransform transform = buildDefaultTransform(local)) {
+                SocketPair<JavaUdpSocket> sockets =
+                        getJavaUdpSocketPair(local, mISM, transform, false);
+                checkUnconnectedUdpSocketPair(sockets.mLeftSock, sockets.mRightSock, local);
+            }
+        }
+    }
+
+    public void testNativeTcpSocketPair() throws Exception {
+        for (String addr : LOOPBACK_ADDRS) {
+            InetAddress local = InetAddress.getByName(addr);
+            try (IpSecTransform transform = buildDefaultTransform(local)) {
+                SocketPair<NativeTcpSocket> sockets =
+                        getNativeTcpSocketPair(local, mISM, transform);
+                checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
+            }
+        }
+    }
+
+    public void testNativeUdpSocketPair() throws Exception {
+        for (String addr : LOOPBACK_ADDRS) {
+            InetAddress local = InetAddress.getByName(addr);
+            try (IpSecTransform transform = buildDefaultTransform(local)) {
+                SocketPair<NativeUdpSocket> sockets =
+                        getNativeUdpSocketPair(local, mISM, transform, true);
+                checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
+            }
+        }
+    }
+
+    public void testNativeUdpSocketPairUnconnected() throws Exception {
+        for (String addr : LOOPBACK_ADDRS) {
+            InetAddress local = InetAddress.getByName(addr);
+            try (IpSecTransform transform = buildDefaultTransform(local)) {
+                SocketPair<NativeUdpSocket> sockets =
+                        getNativeUdpSocketPair(local, mISM, transform, false);
+                checkUnconnectedUdpSocketPair(sockets.mLeftSock, sockets.mRightSock, local);
+            }
+        }
+    }
+}
diff --git a/tests/tests/net/src/android/net/cts/IpSecManagerTest.java b/tests/tests/net/src/android/net/cts/IpSecManagerTest.java
index 7c09e41..95d91a2 100644
--- a/tests/tests/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/IpSecManagerTest.java
@@ -27,30 +27,23 @@
 import android.net.IpSecManager;
 import android.net.IpSecTransform;
 import android.net.TrafficStats;
-import android.os.ParcelFileDescriptor;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.test.AndroidTestCase;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramPacket;
 import java.net.DatagramSocket;
-import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
 import java.net.UnknownHostException;
 import java.util.Arrays;
 
-public class IpSecManagerTest extends AndroidTestCase {
+public class IpSecManagerTest extends IpSecBaseTest {
 
     private static final String TAG = IpSecManagerTest.class.getSimpleName();
 
-    private IpSecManager mISM;
-
     private ConnectivityManager mCM;
 
     private static InetAddress IpAddress(String addrString) {
@@ -70,36 +63,22 @@
     private static final int DROID_SPI = 0xD1201D;
     private static final int MAX_PORT_BIND_ATTEMPTS = 10;
 
-    private static final byte[] CRYPT_KEY = {
-        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-        0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
-        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
-        0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
-    };
-    private static final byte[] AUTH_KEY = {
-        0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
-        0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
-    };
+    private static final byte[] AEAD_KEY = getKey(288);
 
-    private static final String IPV4_LOOPBACK = "127.0.0.1";
-    private static final String IPV6_LOOPBACK = "::1";
     private static final int TCP_HDRLEN_WITH_OPTIONS = 32;
     private static final int UDP_HDRLEN = 8;
     private static final int IP4_HDRLEN = 20;
     private static final int IP6_HDRLEN = 40;
 
-    private static final byte[] TEST_DATA = "Best test data ever!".getBytes();
-
     // Encryption parameters
+    private static final int AES_GCM_IV_LEN = 8;
     private static final int AES_CBC_IV_LEN = 16;
+    private static final int AES_GCM_BLK_SIZE = 4;
     private static final int AES_CBC_BLK_SIZE = 16;
 
     protected void setUp() throws Exception {
         super.setUp();
         mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
-        mISM = (IpSecManager) getContext().getSystemService(Context.IPSEC_SERVICE);
     }
 
     /*
@@ -132,19 +111,6 @@
         }
     }
 
-    private byte[] getAuthKey(int bitLength) {
-        return Arrays.copyOf(AUTH_KEY, bitLength / 8);
-    }
-
-    private static int getDomain(InetAddress address) {
-        int domain;
-        if (address instanceof Inet6Address)
-            domain = OsConstants.AF_INET6;
-        else
-            domain = OsConstants.AF_INET;
-        return domain;
-    }
-
     /** This function finds an available port */
     private static int findUnusedPort() throws Exception {
         // Get an available port.
@@ -180,92 +146,65 @@
 
     private void checkUnconnectedUdp(IpSecTransform transform, InetAddress local, int sendCount,
                                      boolean useJavaSockets) throws Exception {
-        FileDescriptor udpSocket = null;
-        int localPort;
-
+        GenericUdpSocket sockLeft = null, sockRight = null;
         if (useJavaSockets) {
-            DatagramSocket localSocket = new DatagramSocket(0, local);
-            localSocket.setSoTimeout(500);
-            ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(localSocket);
-
-            localPort = localSocket.getLocalPort();
-            udpSocket = pfd.getFileDescriptor();
+            SocketPair<JavaUdpSocket> sockets = getJavaUdpSocketPair(local, mISM, transform, false);
+            sockLeft = sockets.mLeftSock;
+            sockRight = sockets.mRightSock;
         } else {
-            udpSocket = getBoundUdpSocket(local);
-            localPort = getPort(udpSocket);
+            SocketPair<NativeUdpSocket> sockets =
+                    getNativeUdpSocketPair(local, mISM, transform, false);
+            sockLeft = sockets.mLeftSock;
+            sockRight = sockets.mRightSock;
         }
 
-        mISM.applyTransportModeTransform(udpSocket, IpSecManager.DIRECTION_IN, transform);
-        mISM.applyTransportModeTransform(udpSocket, IpSecManager.DIRECTION_OUT, transform);
-
         for (int i = 0; i < sendCount; i++) {
-            byte[] in = new byte[TEST_DATA.length];
-            Os.sendto(udpSocket, TEST_DATA, 0, TEST_DATA.length, 0, local, localPort);
-            Os.read(udpSocket, in, 0, in.length);
-            assertArrayEquals("Encapsulated data did not match.", TEST_DATA, in);
+            byte[] in;
+
+            sockLeft.sendTo(TEST_DATA, local, sockRight.getPort());
+            in = sockRight.receive();
+            assertArrayEquals("Left-to-right encrypted data did not match.", TEST_DATA, in);
+
+            sockRight.sendTo(TEST_DATA, local, sockLeft.getPort());
+            in = sockLeft.receive();
+            assertArrayEquals("Right-to-left encrypted data did not match.", TEST_DATA, in);
         }
 
-        mISM.removeTransportModeTransforms(udpSocket);
-        Os.close(udpSocket);
+        sockLeft.close();
+        sockRight.close();
     }
 
     private void checkTcp(IpSecTransform transform, InetAddress local, int sendCount,
                           boolean useJavaSockets) throws Exception {
-
-        FileDescriptor server = null, client = null;
-
+        GenericTcpSocket client = null, accepted = null;
         if (useJavaSockets) {
-            Socket serverSocket = new Socket();
-            serverSocket.setSoTimeout(500);
-            ParcelFileDescriptor serverPfd = ParcelFileDescriptor.fromSocket(serverSocket);
-            server = serverPfd.getFileDescriptor();
-
-            Socket clientSocket = new Socket();
-            clientSocket.setSoTimeout(500);
-            ParcelFileDescriptor clientPfd = ParcelFileDescriptor.fromSocket(clientSocket);
-            client = clientPfd.getFileDescriptor();
+            SocketPair<JavaTcpSocket> sockets = getJavaTcpSocketPair(local, mISM, transform);
+            client = sockets.mLeftSock;
+            accepted = sockets.mRightSock;
         } else {
-            final int domain = getDomain(local);
-            server =
-              Os.socket(domain, OsConstants.SOCK_STREAM, IPPROTO_TCP);
-            client =
-              Os.socket(domain, OsConstants.SOCK_STREAM, IPPROTO_TCP);
+            SocketPair<NativeTcpSocket> sockets = getNativeTcpSocketPair(local, mISM, transform);
+            client = sockets.mLeftSock;
+            accepted = sockets.mRightSock;
         }
 
-        Os.bind(server, local, 0);
-        int port = ((InetSocketAddress) Os.getsockname(server)).getPort();
-
-        mISM.applyTransportModeTransform(client, IpSecManager.DIRECTION_IN, transform);
-        mISM.applyTransportModeTransform(client, IpSecManager.DIRECTION_OUT, transform);
-        mISM.applyTransportModeTransform(server, IpSecManager.DIRECTION_IN, transform);
-        mISM.applyTransportModeTransform(server, IpSecManager.DIRECTION_OUT, transform);
-
-        Os.listen(server, 10);
-        Os.connect(client, local, port);
-        FileDescriptor accepted = Os.accept(server, null);
-
-        mISM.applyTransportModeTransform(accepted, IpSecManager.DIRECTION_IN, transform);
-        mISM.applyTransportModeTransform(accepted, IpSecManager.DIRECTION_OUT, transform);
-
         // Wait for TCP handshake packets to be counted
         StatsChecker.waitForNumPackets(3); // (SYN, SYN+ACK, ACK)
 
         // Reset StatsChecker, to ignore negotiation overhead.
         StatsChecker.initStatsChecker();
         for (int i = 0; i < sendCount; i++) {
-            byte[] in = new byte[TEST_DATA.length];
+            byte[] in;
 
-            Os.write(client, TEST_DATA, 0, TEST_DATA.length);
-            Os.read(accepted, in, 0, in.length);
+            client.send(TEST_DATA);
+            in = accepted.receive();
             assertArrayEquals("Client-to-server encrypted data did not match.", TEST_DATA, in);
 
             // Allow for newest data + ack packets to be returned before sending next packet
             // Also add the number of expected packets in each of the previous runs (4 per run)
             StatsChecker.waitForNumPackets(2 + (4 * i));
-            in = new byte[TEST_DATA.length];
 
-            Os.write(accepted, TEST_DATA, 0, TEST_DATA.length);
-            Os.read(client, in, 0, in.length);
+            accepted.send(TEST_DATA);
+            in = client.receive();
             assertArrayEquals("Server-to-client encrypted data did not match.", TEST_DATA, in);
 
             // Allow for all data + ack packets to be returned before sending next packet
@@ -273,13 +212,20 @@
             StatsChecker.waitForNumPackets(4 * (i + 1));
         }
 
-        mISM.removeTransportModeTransforms(server);
-        mISM.removeTransportModeTransforms(client);
-        mISM.removeTransportModeTransforms(accepted);
+        // Transforms should not be removed from the sockets, otherwise FIN packets will be sent
+        //     unencrypted.
+        // This test also unfortunately happens to rely on a nuance of the cleanup order. By
+        //     keeping the policy on the socket, but removing the SA before lingering FIN packets
+        //     are sent (at an undetermined later time), the FIN packets are dropped. Without this,
+        //     we run into all kinds of headaches trying to test data accounting (unsolicited
+        //     packets mysteriously appearing and messing up our counters)
+        // The right way to close sockets is to set SO_LINGER to ensure synchronous closure,
+        //     closing the sockets, and then closing the transforms. See documentation for the
+        //     Socket or FileDescriptor flavors of applyTransportModeTransform() in IpSecManager
+        //     for more details.
 
-        Os.close(server);
-        Os.close(client);
-        Os.close(accepted);
+        client.close();
+        accepted.close();
     }
 
     /*
@@ -299,8 +245,7 @@
 
         IpSecTransform transform =
                 new IpSecTransform.Builder(mContext)
-                        .setEncryption(
-                                new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
+                        .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
                         .setAuthentication(
                                 new IpSecAlgorithm(
                                         IpSecAlgorithm.AUTH_HMAC_SHA256,
@@ -363,7 +308,6 @@
 
     /** Snapshot of TrafficStats as of initStatsChecker call for later comparisons */
     private static class StatsChecker {
-        private static final String LOOPBACK_INTERFACE = "lo";
         private static final double ERROR_MARGIN_BYTES = 1.05;
         private static final double ERROR_MARGIN_PKTS = 1.05;
         private static final int MAX_WAIT_TIME_MILLIS = 1000;
@@ -451,7 +395,7 @@
             assertTrue((expectedDelta * errorMargin) > newStats - oldStats);
         }
 
-        private static void initStatsChecker() throws IOException {
+        private static void initStatsChecker() throws Exception {
             uidTxBytes = TrafficStats.getUidTxBytes(Os.getuid());
             uidRxBytes = TrafficStats.getUidRxBytes(Os.getuid());
             uidTxPackets = TrafficStats.getUidTxPackets(Os.getuid());
@@ -464,35 +408,37 @@
         }
     }
 
-    private int getTruncLenBits(IpSecAlgorithm auth) {
-        return auth == null ? 0 : auth.getTruncationLengthBits();
+    private int getTruncLenBits(IpSecAlgorithm authOrAead) {
+        return authOrAead == null ? 0 : authOrAead.getTruncationLengthBits();
     }
 
-    private int getIvLen(IpSecAlgorithm crypt) {
-        if (crypt == null) {
-            return 0;
-        }
+    private int getIvLen(IpSecAlgorithm cryptOrAead) {
+        if (cryptOrAead == null) { return 0; }
 
-        switch (crypt.getName()) {
+        switch (cryptOrAead.getName()) {
             case IpSecAlgorithm.CRYPT_AES_CBC:
                 return AES_CBC_IV_LEN;
+            case IpSecAlgorithm.AUTH_CRYPT_AES_GCM:
+                return AES_GCM_IV_LEN;
             default:
                 throw new IllegalArgumentException(
-                        "IV length unknown for algorithm" + crypt.getName());
+                        "IV length unknown for algorithm" + cryptOrAead.getName());
         }
     }
 
-    private int getBlkSize(IpSecAlgorithm crypt) {
-        if (crypt == null) {
-            return 4;
-        }
+    private int getBlkSize(IpSecAlgorithm cryptOrAead) {
+        // RFC 4303, section 2.4 states that ciphertext plus pad_len, next_header fields must
+        //     terminate on a 4-byte boundary. Thus, the minimum ciphertext block size is 4 bytes.
+        if (cryptOrAead == null) { return 4; }
 
-        switch (crypt.getName()) {
+        switch (cryptOrAead.getName()) {
             case IpSecAlgorithm.CRYPT_AES_CBC:
                 return AES_CBC_BLK_SIZE;
+            case IpSecAlgorithm.AUTH_CRYPT_AES_GCM:
+                return AES_GCM_BLK_SIZE;
             default:
                 throw new IllegalArgumentException(
-                        "Blk size unknown for algorithm" + crypt.getName());
+                        "Blk size unknown for algorithm" + cryptOrAead.getName());
         }
     }
 
@@ -514,6 +460,7 @@
             String localAddress,
             IpSecAlgorithm crypt,
             IpSecAlgorithm auth,
+            IpSecAlgorithm aead,
             boolean doUdpEncap,
             int sendCount,
             boolean useJavaSockets)
@@ -532,6 +479,9 @@
             if (auth != null) {
                 transformBuilder.setAuthentication(auth);
             }
+            if (aead != null) {
+                transformBuilder.setAuthenticatedEncryption(aead);
+            }
 
             if (doUdpEncap) {
                 transformBuilder =
@@ -563,9 +513,9 @@
                     transportHdrLen,
                     udpEncapLen,
                     sendCount,
-                    getIvLen(crypt),
-                    getBlkSize(crypt),
-                    getTruncLenBits(auth));
+                    getIvLen(crypt != null ? crypt : aead),
+                    getBlkSize(crypt != null ? crypt : aead),
+                    getTruncLenBits(auth != null ? auth : aead));
         }
     }
 
@@ -591,16 +541,17 @@
         int expectedInnerBytes = innerPacketSize * sendCount;
         int expectedPackets = sendCount;
 
+        // Each run sends two packets, one in each direction.
+        sendCount *= 2;
+        expectedOuterBytes *= 2;
+        expectedInnerBytes *= 2;
+        expectedPackets *= 2;
+
         // Add TCP ACKs for data packets
         if (protocol == IPPROTO_TCP) {
             int encryptedTcpPktSize =
                     calculateEspPacketSize(TCP_HDRLEN_WITH_OPTIONS, ivLen, blkSize, truncLenBits);
 
-                // Each run sends two packets, one in each direction.
-                sendCount *= 2;
-                expectedOuterBytes *= 2;
-                expectedInnerBytes *= 2;
-                expectedPackets *= 2;
 
                 // Add data packet ACKs
                 expectedOuterBytes += (encryptedTcpPktSize + udpEncapLen + ipHdrLen) * (sendCount);
@@ -625,34 +576,68 @@
         }
     }
 
-    public void testIkeOverUdpEncapSocket() throws Exception {
-        // IPv6 not supported for UDP-encap-ESP
-        InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
+    private void checkIkePacket(
+            NativeUdpSocket wrappedEncapSocket, InetAddress localAddr) throws Exception {
         StatsChecker.initStatsChecker();
 
-        try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
-            int localPort = getPort(encapSocket.getSocket());
+        try (NativeUdpSocket remoteSocket = new NativeUdpSocket(getBoundUdpSocket(localAddr))) {
 
-            // Append ESP header - 4 bytes of SPI, 4 bytes of seq number
+            // Append IKE/ESP header - 4 bytes of SPI, 4 bytes of seq number, all zeroed out
+            // If the first four bytes are zero, assume non-ESP (IKE traffic)
             byte[] dataWithEspHeader = new byte[TEST_DATA.length + 8];
             System.arraycopy(TEST_DATA, 0, dataWithEspHeader, 8, TEST_DATA.length);
 
-            byte[] in = new byte[dataWithEspHeader.length];
-            Os.sendto(
-                    encapSocket.getSocket(),
-                    dataWithEspHeader,
-                    0,
-                    dataWithEspHeader.length,
-                    0,
-                    local,
-                    localPort);
-            Os.read(encapSocket.getSocket(), in, 0, in.length);
+            // Send the IKE packet from remoteSocket to wrappedEncapSocket. Since IKE packets
+            // are multiplexed over the socket, we expect them to appear on the encap socket
+            // (as opposed to being decrypted and received on the non-encap socket)
+            remoteSocket.sendTo(dataWithEspHeader, localAddr, wrappedEncapSocket.getPort());
+            byte[] in = wrappedEncapSocket.receive();
             assertArrayEquals("Encapsulated data did not match.", dataWithEspHeader, in);
 
-            int ipHdrLen = local instanceof Inet6Address ? IP6_HDRLEN : IP4_HDRLEN;
-            int expectedPacketSize = dataWithEspHeader.length + UDP_HDRLEN + ipHdrLen;
-            StatsChecker.assertUidStatsDelta(expectedPacketSize, 1, expectedPacketSize, 1);
-            StatsChecker.assertIfaceStatsDelta(expectedPacketSize, 1, expectedPacketSize, 1);
+            // Also test that the IKE socket can send data out.
+            wrappedEncapSocket.sendTo(dataWithEspHeader, localAddr, remoteSocket.getPort());
+            in = remoteSocket.receive();
+            assertArrayEquals("Encapsulated data did not match.", dataWithEspHeader, in);
+
+            // Calculate expected packet sizes. Always use IPv4 header, since our kernels only
+            // guarantee support of UDP encap on IPv4.
+            int expectedNumPkts = 2;
+            int expectedPacketSize =
+                    expectedNumPkts * (dataWithEspHeader.length + UDP_HDRLEN + IP4_HDRLEN);
+
+            StatsChecker.waitForNumPackets(expectedNumPkts);
+            StatsChecker.assertUidStatsDelta(
+                    expectedPacketSize, expectedNumPkts, expectedPacketSize, expectedNumPkts);
+            StatsChecker.assertIfaceStatsDelta(
+                    expectedPacketSize, expectedNumPkts, expectedPacketSize, expectedNumPkts);
+        }
+    }
+
+    public void testIkeOverUdpEncapSocket() throws Exception {
+        // IPv6 not supported for UDP-encap-ESP
+        InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
+        try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+            NativeUdpSocket wrappedEncapSocket =
+                    new NativeUdpSocket(encapSocket.getFileDescriptor());
+            checkIkePacket(wrappedEncapSocket, local);
+
+            // Now try with a transform applied to a socket using this Encap socket
+            IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
+            IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+
+            try (IpSecManager.SecurityParameterIndex spi =
+                            mISM.allocateSecurityParameterIndex(local);
+                    IpSecTransform transform =
+                            new IpSecTransform.Builder(mContext)
+                                    .setEncryption(crypt)
+                                    .setAuthentication(auth)
+                                    .setIpv4Encapsulation(encapSocket, encapSocket.getPort())
+                                    .buildTransportModeTransform(local, spi);
+                    JavaUdpSocket localSocket = new JavaUdpSocket(local)) {
+                applyTransformBidirectionally(mISM, transform, localSocket);
+
+                checkIkePacket(wrappedEncapSocket, local);
+            }
         }
     }
 
@@ -668,346 +653,448 @@
     // public void testInterfaceCountersTcp4() throws Exception {
     //     IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
     //     IpSecAlgorithm auth = new IpSecAlgorithm(
-    //             IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
+    //             IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
     //     checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1000);
     // }
 
     // public void testInterfaceCountersTcp6() throws Exception {
     //     IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
     //     IpSecAlgorithm auth = new IpSecAlgorithm(
-    //             IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
+    //             IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
     //     checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1000);
     // }
 
     // public void testInterfaceCountersTcp4UdpEncap() throws Exception {
     //     IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
     //     IpSecAlgorithm auth =
-    //             new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
+    //             new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
     //     checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1000);
     // }
 
     public void testInterfaceCountersUdp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1000, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1000, false);
     }
 
     public void testInterfaceCountersUdp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1000, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1000, false);
     }
 
     public void testInterfaceCountersUdp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, true, 1000, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1000, false);
     }
 
     public void testAesCbcHmacMd5Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacMd5Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacMd5Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacMd5Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha1Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha1Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha1Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha1Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha256Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha256Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha256Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha256Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha384Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha384Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha384Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha384Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha512Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha512Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha512Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
     public void testAesCbcHmacSha512Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(
-                IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    public void testAesGcm64Tcp4() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm64Tcp6() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm64Udp4() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm64Udp6() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm96Tcp4() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm96Tcp6() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm96Udp4() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm96Udp6() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm128Tcp4() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm128Tcp6() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm128Udp4() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    public void testAesGcm128Udp6() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
     public void testAesCbcHmacMd5Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
     public void testAesCbcHmacMd5Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
     public void testAesCbcHmacSha1Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
     public void testAesCbcHmacSha1Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
     public void testAesCbcHmacSha256Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
     public void testAesCbcHmacSha256Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
     public void testAesCbcHmacSha384Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
     public void testAesCbcHmacSha384Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
     public void testAesCbcHmacSha512Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
     public void testAesCbcHmacSha512Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, true, 1, false);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
+    }
+
+    public void testAesGcm64Tcp4UdpEncap() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
+    }
+
+    public void testAesGcm64Udp4UdpEncap() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
+    }
+
+    public void testAesGcm96Tcp4UdpEncap() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
+    }
+
+    public void testAesGcm96Udp4UdpEncap() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
+    }
+
+    public void testAesGcm128Tcp4UdpEncap() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
+    }
+
+    public void testAesGcm128Udp4UdpEncap() throws Exception {
+        IpSecAlgorithm authCrypt =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
     }
 
     public void testCryptUdp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, false, 1, true);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, false, 1, true);
     }
 
     public void testAuthUdp4() throws Exception {
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, false, 1, true);
     }
 
     public void testCryptUdp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, null, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, null, false, 1, true);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, null, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, null, null, false, 1, true);
     }
 
     public void testAuthUdp6() throws Exception {
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, auth, false, 1, false);
-        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, auth, null, false, 1, true);
     }
 
     public void testCryptTcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, false, 1, true);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, false, 1, true);
     }
 
     public void testAuthTcp4() throws Exception {
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, false, 1, true);
     }
 
     public void testCryptTcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, null, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, null, false, 1, true);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, null, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, null, null, false, 1, true);
     }
 
     public void testAuthTcp6() throws Exception {
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, auth, false, 1, false);
-        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, auth, false, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, auth, null, false, 1, true);
     }
 
     public void testCryptUdp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, true, 1, false);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, true, 1, true);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, true, 1, true);
     }
 
     public void testAuthUdp4UdpEncap() throws Exception {
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, true, 1, false);
-        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, true, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, true, 1, true);
     }
 
     public void testCryptTcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, true, 1, false);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, true, 1, true);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, true, 1, true);
     }
 
     public void testAuthTcp4UdpEncap() throws Exception {
-        IpSecAlgorithm auth =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, true, 1, false);
-        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, true, 1, true);
+        IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, true, 1, true);
     }
 
     public void testOpenUdpEncapSocketSpecificPort() throws Exception {
@@ -1043,153 +1130,4 @@
             assertTrue("Returned invalid port", encapSocket.getPort() != 0);
         }
     }
-
-    public void testUdpEncapsulation() throws Exception {
-        InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
-
-        // TODO: Refactor to make this more representative of a normal application use case. (use
-        // separate sockets for inbound and outbound)
-        // Create SPIs, UDP encap socket
-        try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket();
-                IpSecManager.SecurityParameterIndex spi =
-                        mISM.allocateSecurityParameterIndex(local);
-                IpSecTransform transform =
-                        buildIpSecTransform(mContext, spi, encapSocket, local)) {
-
-            // Create user socket, apply transform to it
-            FileDescriptor udpSocket = null;
-            try {
-                udpSocket = getBoundUdpSocket(local);
-                int port = getPort(udpSocket);
-
-                mISM.applyTransportModeTransform(
-                        udpSocket, IpSecManager.DIRECTION_IN, transform);
-                mISM.applyTransportModeTransform(
-                        udpSocket, IpSecManager.DIRECTION_OUT, transform);
-
-                // Send an ESP packet from this socket to itself. Since the inbound and
-                // outbound transforms match, we should receive the data we sent.
-                byte[] data = new String("IPSec UDP-encap-ESP test data").getBytes("UTF-8");
-                Os.sendto(udpSocket, data, 0, data.length, 0, local, port);
-                byte[] in = new byte[data.length];
-                Os.read(udpSocket, in, 0, in.length);
-                assertTrue("Encapsulated data did not match.", Arrays.equals(data, in));
-
-                // Send an IKE packet from this socket to itself. IKE packets (SPI of 0)
-                // are not transformed in any way, and should be sent in the clear
-                // We expect this to work too (no inbound transforms)
-                final byte[] header = new byte[] {0, 0, 0, 0};
-                final String message = "Sample IKE Packet";
-                data = (new String(header) + message).getBytes("UTF-8");
-                Os.sendto(
-                        encapSocket.getSocket(),
-                        data,
-                        0,
-                        data.length,
-                        0,
-                        local,
-                        encapSocket.getPort());
-                in = new byte[data.length];
-                Os.read(encapSocket.getSocket(), in, 0, in.length);
-                assertTrue(
-                        "Encap socket was unable to send/receive IKE data",
-                        Arrays.equals(data, in));
-
-                mISM.removeTransportModeTransforms(udpSocket);
-            } finally {
-                if (udpSocket != null) {
-                    Os.close(udpSocket);
-                }
-            }
-        }
-    }
-
-    public void testIke() throws Exception {
-        InetAddress localAddr = InetAddress.getByName(IPV4_LOOPBACK);
-
-        // TODO: Refactor to make this more representative of a normal application use case. (use
-        // separate sockets for inbound and outbound)
-        try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket();
-                IpSecManager.SecurityParameterIndex spi =
-                        mISM.allocateSecurityParameterIndex(localAddr);
-                IpSecTransform transform =
-                        buildIpSecTransform(mContext, spi, encapSocket, localAddr)) {
-
-            // Create user socket, apply transform to it
-            FileDescriptor sock = null;
-
-            try {
-                sock = getBoundUdpSocket(localAddr);
-                int port = getPort(sock);
-
-                mISM.applyTransportModeTransform(sock, IpSecManager.DIRECTION_IN, transform);
-                mISM.applyTransportModeTransform(sock, IpSecManager.DIRECTION_OUT, transform);
-
-                // TODO: Find a way to set a timeout on the socket, and assert the ESP packet
-                // doesn't make it through. Setting sockopts currently throws EPERM (possibly
-                // because it is owned by a different UID).
-
-                // Send ESP packet from our socket to the encap socket. The SPIs do not
-                // match, and we should expect this packet to be dropped.
-                byte[] header = new byte[] {1, 1, 1, 1};
-                String message = "Sample ESP Packet";
-                byte[] data = (new String(header) + message).getBytes("UTF-8");
-                Os.sendto(sock, data, 0, data.length, 0, localAddr, encapSocket.getPort());
-
-                // Send IKE packet from the encap socket to itself. Since IKE is not
-                // transformed in any way, this should succeed.
-                header = new byte[] {0, 0, 0, 0};
-                message = "Sample IKE Packet";
-                data = (new String(header) + message).getBytes("UTF-8");
-                Os.sendto(
-                        encapSocket.getSocket(),
-                        data,
-                        0,
-                        data.length,
-                        0,
-                        localAddr,
-                        encapSocket.getPort());
-
-                // ESP data should be dropped, due to different input SPI (as opposed to being
-                // readable from the encapSocket)
-                // Thus, only IKE data should be received from the socket.
-                // If the first four bytes are zero, assume non-ESP (IKE) traffic.
-                // Expect an nulled out SPI just as we sent out, without being modified.
-                byte[] in = new byte[4];
-                in[0] = 1; // Make sure the array has to be overwritten to pass
-                Os.read(encapSocket.getSocket(), in, 0, in.length);
-                assertTrue(
-                        "Encap socket received UDP-encap-ESP data despite invalid SPIs",
-                        Arrays.equals(header, in));
-
-                mISM.removeTransportModeTransforms(sock);
-            } finally {
-                if (sock != null) {
-                    Os.close(sock);
-                }
-            }
-        }
-    }
-
-    private static IpSecTransform buildIpSecTransform(
-            Context mContext,
-            IpSecManager.SecurityParameterIndex spi,
-            IpSecManager.UdpEncapsulationSocket encapSocket,
-            InetAddress remoteAddr)
-            throws Exception {
-            String localAddr = (remoteAddr instanceof Inet4Address)
-                    ? IPV4_LOOPBACK : IPV6_LOOPBACK;
-        return new IpSecTransform.Builder(mContext)
-                .setEncryption(
-                        new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
-                .setAuthentication(
-                        new IpSecAlgorithm(
-                                IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4))
-                .setIpv4Encapsulation(encapSocket, encapSocket.getPort())
-                .buildTransportModeTransform(InetAddress.getByName(localAddr), spi);
-    }
-
-    private static int getPort(FileDescriptor sock) throws Exception {
-        return ((InetSocketAddress) Os.getsockname(sock)).getPort();
-    }
 }
diff --git a/tests/tests/print/AndroidManifest.xml b/tests/tests/print/AndroidManifest.xml
index c5148c8..b398db7 100644
--- a/tests/tests/print/AndroidManifest.xml
+++ b/tests/tests/print/AndroidManifest.xml
@@ -25,7 +25,8 @@
 
         <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="android.print.cts.PrintDocumentActivity"/>
+        <activity android:name="android.print.cts.PrintDocumentActivity"
+                  android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"/>
 
         <service
             android:name="android.print.cts.services.FirstPrintService"
diff --git a/tests/tests/print/src/android/print/cts/BasePrintTest.java b/tests/tests/print/src/android/print/cts/BasePrintTest.java
index db1efd7..a352ca6 100644
--- a/tests/tests/print/src/android/print/cts/BasePrintTest.java
+++ b/tests/tests/print/src/android/print/cts/BasePrintTest.java
@@ -133,7 +133,7 @@
      *
      * @return the UI device
      */
-    public UiDevice getUiDevice() {
+    public static UiDevice getUiDevice() {
         return UiDevice.getInstance(getInstrumentation());
     }
 
@@ -196,6 +196,13 @@
 
         Instrumentation instrumentation = getInstrumentation();
 
+        // Prevent rotation
+        getUiDevice().freezeRotation();
+        while (!getUiDevice().isNaturalOrientation()) {
+            getUiDevice().setOrientationNatural();
+            getUiDevice().waitForIdle();
+        }
+
         // Make sure we start with a clean slate.
         Log.d(LOG_TAG, "clearPrintSpoolerData()");
         clearPrintSpoolerData();
@@ -313,6 +320,9 @@
         SystemUtil.runShellCommand(instrumentation, "settings put secure "
                     + Settings.Secure.DISABLED_PRINT_SERVICES + " null");
 
+        // Allow rotation
+        getUiDevice().unfreezeRotation();
+
         Log.d(LOG_TAG, "tearDownClass() done");
     }
 
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java b/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java
index c3e6e96..8c0cb8a 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.app.KeyguardManager;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.WindowManager;
@@ -45,6 +46,13 @@
     }
 
     @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        Log.d(LOG_TAG, "onConfigurationChanged(" + newConfig + ")");
+    }
+
+    @Override
     protected void onDestroy() {
         Log.d(LOG_TAG, "onDestroy() " + this);
         BasePrintTest.onActivityDestroyCalled(mTestId);
diff --git a/tests/tests/telephony/EmbmsMiddlewareTestApp/src/android/telephony/cts/embmstestapp/CtsDownloadService.java b/tests/tests/telephony/EmbmsMiddlewareTestApp/src/android/telephony/cts/embmstestapp/CtsDownloadService.java
index 06e88c0..30447b8 100644
--- a/tests/tests/telephony/EmbmsMiddlewareTestApp/src/android/telephony/cts/embmstestapp/CtsDownloadService.java
+++ b/tests/tests/telephony/EmbmsMiddlewareTestApp/src/android/telephony/cts/embmstestapp/CtsDownloadService.java
@@ -31,8 +31,9 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.telephony.MbmsDownloadSession;
+import android.telephony.mbms.DownloadProgressListener;
 import android.telephony.mbms.DownloadRequest;
-import android.telephony.mbms.DownloadStateCallback;
+import android.telephony.mbms.DownloadStatusListener;
 import android.telephony.mbms.FileInfo;
 import android.telephony.mbms.FileServiceInfo;
 import android.telephony.mbms.MbmsDownloadSessionCallback;
@@ -91,15 +92,30 @@
             ComponentName.unflattenFromString(
                     "android.telephony.cts/android.telephony.mbms.MbmsDownloadReceiver");
 
-    public static final Uri DOWNLOAD_SOURCE_URI = Uri.parse("http://www.example.com/file_download");
+    public static final Uri DOWNLOAD_SOURCE_URI_ROOT =
+            Uri.parse("http://www.example.com/file_download");
     public static final FileServiceInfo FILE_SERVICE_INFO;
-    public static final FileInfo FILE_INFO = new FileInfo(
-            DOWNLOAD_SOURCE_URI.buildUpon().appendPath("file1.txt").build(),
+    public static final FileInfo FILE_INFO_1 = new FileInfo(
+            DOWNLOAD_SOURCE_URI_ROOT.buildUpon().appendPath("file1.txt").build(),
+            "text/plain");
+    public static final FileInfo FILE_INFO_2 = new FileInfo(
+            DOWNLOAD_SOURCE_URI_ROOT.buildUpon().appendPath("sub_dir1")
+                    .appendPath("sub_dir2")
+                    .appendPath("file2.txt")
+                    .build(),
             "text/plain");
     public static final byte[] SAMPLE_FILE_DATA = "this is some sample file data".getBytes();
 
+    // Define allowed source URIs so that we don't have to do the prefix matching calculation
+    public static final Uri SOURCE_URI_1 = DOWNLOAD_SOURCE_URI_ROOT.buildUpon()
+            .appendPath("file1.txt").build();
+    public static final Uri SOURCE_URI_2 = DOWNLOAD_SOURCE_URI_ROOT.buildUpon()
+            .appendPath("sub_dir1").appendPath("*").build();
+    public static final Uri SOURCE_URI_3 = DOWNLOAD_SOURCE_URI_ROOT.buildUpon()
+            .appendPath("*").build();
+
     static {
-        String id = "FileServiceId";
+        String id = "urn:3GPP:service_0-0";
         Map<Locale, String> localeDict = new HashMap<Locale, String>() {{
             put(Locale.US, "Entertainment Source 1");
             put(Locale.CANADA, "Entertainment Source 1, eh?");
@@ -108,13 +124,18 @@
             add(Locale.CANADA);
             add(Locale.US);
         }};
+        List<FileInfo> files = new ArrayList<FileInfo>() {{
+            add(FILE_INFO_1);
+            add(FILE_INFO_2);
+        }};
         FILE_SERVICE_INFO = new FileServiceInfo(localeDict, "class1", locales,
                 id, new Date(2017, 8, 21, 18, 20, 29),
-                new Date(2017, 8, 21, 18, 23, 9), Collections.singletonList(FILE_INFO));
+                new Date(2017, 8, 21, 18, 23, 9), files);
     }
 
     private MbmsDownloadSessionCallback mAppCallback;
-    private DownloadStateCallback mDownloadStateCallback;
+    private DownloadStatusListener mDownloadStatusListener;
+    private DownloadProgressListener mDownloadProgressListener;
 
     private HandlerThread mHandlerThread;
     private Handler mHandler;
@@ -203,9 +224,16 @@
         }
 
         @Override
-        public int registerStateCallback(DownloadRequest downloadRequest,
-                DownloadStateCallback listener) throws RemoteException {
-            mDownloadStateCallback = listener;
+        public int addProgressListener(DownloadRequest downloadRequest,
+                DownloadProgressListener listener) throws RemoteException {
+            mDownloadProgressListener = listener;
+            return MbmsErrors.SUCCESS;
+        }
+
+        @Override
+        public int addStatusListener(DownloadRequest downloadRequest,
+                DownloadStatusListener listener) throws RemoteException {
+            mDownloadStatusListener = listener;
             return MbmsErrors.SUCCESS;
         }
 
@@ -243,9 +271,9 @@
         }
 
         @Override
-        public int unregisterStateCallback(DownloadRequest downloadRequest,
-                DownloadStateCallback callback) {
-            mDownloadStateCallback = null;
+        public int removeStatusListener(DownloadRequest downloadRequest,
+                DownloadStatusListener callback) {
+            mDownloadStatusListener = null;
             return MbmsErrors.SUCCESS;
         }
 
@@ -272,7 +300,7 @@
             mAppCallback = null;
             mErrorCodeOverride = MbmsErrors.SUCCESS;
             mReceivedRequests.clear();
-            mDownloadStateCallback = null;
+            mDownloadStatusListener = null;
             mTempFileRootDirPath = null;
         }
 
@@ -295,28 +323,30 @@
         public void fireOnProgressUpdated(DownloadRequest request, FileInfo fileInfo,
                 int currentDownloadSize, int fullDownloadSize,
                 int currentDecodedSize, int fullDecodedSize) {
-            if (mDownloadStateCallback == null) {
+            if (mDownloadStatusListener == null) {
                 return;
             }
-            mHandler.post(() -> mDownloadStateCallback.onProgressUpdated(request, fileInfo,
+            mHandler.post(() -> mDownloadProgressListener.onProgressUpdated(request, fileInfo,
                     currentDownloadSize, fullDownloadSize, currentDecodedSize, fullDecodedSize));
         }
 
         @Override
         public void fireOnStateUpdated(DownloadRequest request, FileInfo fileInfo, int state) {
-            if (mDownloadStateCallback == null) {
+            if (mDownloadStatusListener == null) {
                 return;
             }
-            mHandler.post(() -> mDownloadStateCallback.onStateUpdated(request, fileInfo, state));
+            mHandler.post(() -> mDownloadStatusListener.onStatusUpdated(request, fileInfo, state));
         }
 
         @Override
         public void actuallyStartDownloadFlow() {
             DownloadRequest request = mReceivedRequests.get(0);
+            List<FileInfo> requestedFiles = getRequestedFiles(request);
             // Compose the FILE_DESCRIPTOR_REQUEST_INTENT to get some FDs to write to
             Intent requestIntent = new Intent(VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST);
             requestIntent.putExtra(VendorUtils.EXTRA_SERVICE_ID, request.getFileServiceId());
-            requestIntent.putExtra(VendorUtils.EXTRA_FD_COUNT, 1);
+
+            requestIntent.putExtra(VendorUtils.EXTRA_FD_COUNT, requestedFiles.size());
             requestIntent.putExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT, mTempFileRootDirPath);
             requestIntent.setComponent(CTS_TEST_RECEIVER_COMPONENT);
 
@@ -330,52 +360,36 @@
                         public void onReceive(Context context, Intent intent) {
                             logd("Got file-descriptors");
                             Bundle extras = getResultExtras(false);
-                            UriPathPair tempFile = (UriPathPair) extras.getParcelableArrayList(
-                                    VendorUtils.EXTRA_FREE_URI_LIST).get(0);
-                            int result = MbmsDownloadSession.RESULT_SUCCESSFUL;
-                            try {
-                                ParcelFileDescriptor tempFileFd =
-                                        getContentResolver().openFileDescriptor(
-                                                tempFile.getContentUri(), "rw");
-                                OutputStream destinationStream =
-                                        new ParcelFileDescriptor.AutoCloseOutputStream(tempFileFd);
+                            List<UriPathPair> tempFiles = extras.getParcelableArrayList(
+                                    VendorUtils.EXTRA_FREE_URI_LIST);
 
-                                destinationStream.write(SAMPLE_FILE_DATA);
-                                destinationStream.flush();
-                            } catch (IOException e) {
-                                result = MbmsDownloadSession.RESULT_CANCELLED;
-                            }
+                            for (int i = 0; i < tempFiles.size(); i++) {
+                                UriPathPair tempFile = tempFiles.get(i);
+                                FileInfo requestedFile = requestedFiles.get(i);
+                                int result = writeContentsToTempFile(tempFile);
 
-                            Intent downloadResultIntent =
-                                    new Intent(VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL);
-                            downloadResultIntent.putExtra(
-                                    MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
-                            downloadResultIntent.putExtra(VendorUtils.EXTRA_FINAL_URI,
-                                    tempFile.getFilePathUri());
-                            downloadResultIntent.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO,
-                                    FILE_INFO);
-                            downloadResultIntent.putExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT,
-                                    mTempFileRootDirPath);
-                            downloadResultIntent.putExtra(
-                                    MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
-                            downloadResultIntent.setComponent(CTS_TEST_RECEIVER_COMPONENT);
+                                Intent downloadResultIntent = composeDownloadResultIntent(
+                                        tempFile, request, result, requestedFile);
 
-                            logd("Sending broadcast to app: " + downloadResultIntent.toString());
-                            sendOrderedBroadcast(downloadResultIntent,
-                                    null, // receiverPermission
-                                    new BroadcastReceiver() {
-                                        @Override
-                                        public void onReceive(Context context, Intent intent) {
-                                            Bundle b = new Bundle();
-                                            b.putString(METHOD_NAME, METHOD_DOWNLOAD_RESULT_ACK);
-                                            b.putInt(ARGUMENT_RESULT_CODE, getResultCode());
-                                            mReceivedCalls.add(b);
-                                        }
-                                    },
-                                    null, // scheduler
-                                    Activity.RESULT_OK,
-                                    null, // initialData
-                                    null /* initialExtras */);
+                                logd("Sending broadcast to app: "
+                                        + downloadResultIntent.toString());
+                                sendOrderedBroadcast(downloadResultIntent,
+                                        null, // receiverPermission
+                                        new BroadcastReceiver() {
+                                            @Override
+                                            public void onReceive(Context context, Intent intent) {
+                                                Bundle b = new Bundle();
+                                                b.putString(METHOD_NAME,
+                                                        METHOD_DOWNLOAD_RESULT_ACK);
+                                                b.putInt(ARGUMENT_RESULT_CODE, getResultCode());
+                                                mReceivedCalls.add(b);
+                                            }
+                                        },
+                                        null, // scheduler
+                                        Activity.RESULT_OK,
+                                        null, // initialData
+                                        null /* initialExtras */);
+                        }
                         }
                     },
                     mHandler, // scheduler
@@ -386,6 +400,54 @@
         }
     };
 
+    private List<FileInfo> getRequestedFiles(DownloadRequest request) {
+        if (SOURCE_URI_1.equals(request.getSourceUri())) {
+            return Collections.singletonList(FILE_INFO_1);
+        }
+        if (SOURCE_URI_2.equals(request.getSourceUri())) {
+            return Collections.singletonList(FILE_INFO_2);
+        }
+        if (SOURCE_URI_3.equals(request.getSourceUri())) {
+            return FILE_SERVICE_INFO.getFiles();
+        }
+        return Collections.emptyList();
+    }
+
+    private Intent composeDownloadResultIntent(UriPathPair tempFile, DownloadRequest request,
+            int result, FileInfo downloadedFile) {
+        Intent downloadResultIntent =
+                new Intent(VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL);
+        downloadResultIntent.putExtra(
+                MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
+        downloadResultIntent.putExtra(VendorUtils.EXTRA_FINAL_URI,
+                tempFile.getFilePathUri());
+        downloadResultIntent.putExtra(
+                MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, downloadedFile);
+        downloadResultIntent.putExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT,
+                mTempFileRootDirPath);
+        downloadResultIntent.putExtra(
+                MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
+        downloadResultIntent.setComponent(CTS_TEST_RECEIVER_COMPONENT);
+        return downloadResultIntent;
+    }
+
+    private int writeContentsToTempFile(UriPathPair tempFile) {
+        int result = MbmsDownloadSession.RESULT_SUCCESSFUL;
+        try {
+            ParcelFileDescriptor tempFileFd =
+                    getContentResolver().openFileDescriptor(
+                            tempFile.getContentUri(), "rw");
+            OutputStream destinationStream =
+                    new ParcelFileDescriptor.AutoCloseOutputStream(tempFileFd);
+
+            destinationStream.write(SAMPLE_FILE_DATA);
+            destinationStream.flush();
+        } catch (IOException e) {
+            result = MbmsDownloadSession.RESULT_CANCELLED;
+        }
+        return result;
+    }
+
     @Override
     public void onDestroy() {
         super.onCreate();
diff --git a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadCallbackTest.java b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadCallbackTest.java
new file mode 100644
index 0000000..98b7ec4
--- /dev/null
+++ b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadCallbackTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.embms.cts;
+
+import android.telephony.MbmsDownloadSession;
+import android.telephony.cts.embmstestapp.CtsDownloadService;
+import android.telephony.mbms.DownloadProgressListener;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStatusListener;
+import android.telephony.mbms.FileInfo;
+
+import com.android.internal.os.SomeArgs;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class MbmsDownloadCallbackTest extends MbmsDownloadTestBase {
+    private static final long SHORT_TIMEOUT = 500;
+
+    private class TestDSCallback extends DownloadStatusListener {
+        private final BlockingQueue<SomeArgs> mCalls = new LinkedBlockingQueue<>();
+
+        @Override
+        public void onStatusUpdated(DownloadRequest request, FileInfo fileInfo,
+                @MbmsDownloadSession.DownloadStatus int state) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = request;
+            args.arg2 = fileInfo;
+            args.arg3 = state;
+            mCalls.add(args);
+        }
+
+        public SomeArgs waitOnStatusUpdated(long timeout) {
+            try {
+                return mCalls.poll(timeout, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return null;
+            }
+        }
+    }
+
+    private class TestDPCallback extends DownloadProgressListener {
+        private final BlockingQueue<SomeArgs> mProgressUpdatedCalls = new LinkedBlockingQueue<>();
+
+        @Override
+        public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
+                currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
+                fullDecodedSize) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = request;
+            args.arg2 = fileInfo;
+            args.arg3 = currentDownloadSize;
+            args.arg4 = fullDownloadSize;
+            args.arg5 = currentDecodedSize;
+            args.arg6 = fullDecodedSize;
+            mProgressUpdatedCalls.add(args);
+        }
+
+        public SomeArgs waitOnProgressUpdated(long timeout) {
+            try {
+                return mProgressUpdatedCalls.poll(timeout, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return null;
+            }
+        }
+    }
+
+    public void testFullCallback() throws Exception {
+        int sampleInt = 10;
+        TestDSCallback statusCallback = new TestDSCallback();
+        TestDPCallback progressCallback = new TestDPCallback();
+        DownloadRequest request = downloadRequestTemplate.build();
+        mDownloadSession.addStatusListener(request, mCallbackExecutor, statusCallback);
+        mDownloadSession.addProgressListener(request, mCallbackExecutor, progressCallback);
+        mMiddlewareControl.fireOnProgressUpdated(request, CtsDownloadService.FILE_INFO_1,
+                sampleInt, sampleInt, sampleInt, sampleInt);
+        SomeArgs progressArgs = progressCallback.waitOnProgressUpdated(ASYNC_TIMEOUT);
+        assertEquals(request, progressArgs.arg1);
+        assertEquals(CtsDownloadService.FILE_INFO_1, progressArgs.arg2);
+        assertEquals(sampleInt, progressArgs.arg3);
+        assertEquals(sampleInt, progressArgs.arg4);
+        assertEquals(sampleInt, progressArgs.arg5);
+        assertEquals(sampleInt, progressArgs.arg6);
+
+        mMiddlewareControl.fireOnStateUpdated(request, CtsDownloadService.FILE_INFO_1, sampleInt);
+        SomeArgs stateArgs = statusCallback.waitOnStatusUpdated(ASYNC_TIMEOUT);
+        assertEquals(request, stateArgs.arg1);
+        assertEquals(CtsDownloadService.FILE_INFO_1, stateArgs.arg2);
+        assertEquals(sampleInt, stateArgs.arg3);
+    }
+
+    public void testDeregistration() throws Exception {
+        TestDSCallback statusCallback = new TestDSCallback();
+        TestDPCallback progressCallback = new TestDPCallback();
+        DownloadRequest request = downloadRequestTemplate.build();
+        mDownloadSession.addProgressListener(request, mCallbackExecutor, progressCallback);
+        mDownloadSession.addStatusListener(request, mCallbackExecutor, statusCallback);
+        mDownloadSession.removeProgressListener(request, progressCallback);
+        mDownloadSession.removeStatusListener(request, statusCallback);
+
+        mMiddlewareControl.fireOnStateUpdated(null, null, 0);
+        assertNull(statusCallback.waitOnStatusUpdated(SHORT_TIMEOUT));
+        mMiddlewareControl.fireOnProgressUpdated(null, null, 0, 0, 0, 0);
+        assertNull(progressCallback.waitOnProgressUpdated(SHORT_TIMEOUT));
+    }
+}
diff --git a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadFlowTest.java b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadFlowTest.java
index e6a97ed..119ac66 100644
--- a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadFlowTest.java
+++ b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadFlowTest.java
@@ -22,6 +22,7 @@
 import android.telephony.MbmsDownloadSession;
 import android.telephony.cts.embmstestapp.CtsDownloadService;
 import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.FileInfo;
 import android.telephony.mbms.MbmsDownloadReceiver;
 
 import java.io.File;
@@ -30,14 +31,10 @@
 
 public class MbmsDownloadFlowTest extends MbmsDownloadTestBase {
     private File tempFileRootDir;
-    private DownloadRequest testDownloadRequest;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        testDownloadRequest = downloadRequestTemplate
-                .setAppIntent(new Intent(MbmsDownloadReceiverTest.APP_INTENT_ACTION))
-                .build();
         tempFileRootDir = new File(mContext.getFilesDir(), "CtsTestDir");
         tempFileRootDir.mkdir();
         mDownloadSession.setTempFileRootDirectory(tempFileRootDir);
@@ -50,34 +47,109 @@
         super.tearDown();
     }
 
-    public void testFileDownloadFlow() throws Exception {
+    public void testSingleFileDownloadFlow() throws Exception {
         MbmsDownloadReceiverTest.AppIntentCapture captor =
                 new MbmsDownloadReceiverTest.AppIntentCapture(mContext, mHandler);
-        mDownloadSession.download(testDownloadRequest);
+        DownloadRequest request = downloadRequestTemplate
+                .setAppIntent(new Intent(MbmsDownloadReceiverTest.APP_INTENT_ACTION))
+                .build();
+        mDownloadSession.download(request);
         mMiddlewareControl.actuallyStartDownloadFlow();
-        Intent downloadDoneIntent = captor.getIntent();
 
+        Uri fileUri = checkReceivedDownloadCompleteIntent(captor.getIntent(), request,
+                CtsDownloadService.FILE_INFO_1);
+        checkFileContentIntegrity(CtsDownloadService.FILE_INFO_1, fileUri);
+        checkDownloadResultAck(1);
+    }
+
+    public void testFileInSubdirectoryDownloadFlow() throws Exception {
+        MbmsDownloadReceiverTest.AppIntentCapture captor =
+                new MbmsDownloadReceiverTest.AppIntentCapture(mContext, mHandler);
+        DownloadRequest request = new DownloadRequest.Builder(
+                CtsDownloadService.SOURCE_URI_2, destinationDirectoryUri)
+                .setServiceInfo(CtsDownloadService.FILE_SERVICE_INFO)
+                .setAppIntent(new Intent(MbmsDownloadReceiverTest.APP_INTENT_ACTION))
+                .build();
+
+        mDownloadSession.download(request);
+        mMiddlewareControl.actuallyStartDownloadFlow();
+
+        Uri fileUri = checkReceivedDownloadCompleteIntent(captor.getIntent(), request,
+                CtsDownloadService.FILE_INFO_2);
+        // Make sure that the received file is placed in the proper subdirectory.
+        String file2RelativePath = CtsDownloadService.FILE_INFO_2.getUri().getPath().substring(
+                CtsDownloadService.SOURCE_URI_2.getPath().length());
+        assertTrue("got path: " + fileUri.getPath() + ", should end with: " + file2RelativePath,
+                fileUri.getPath().endsWith(file2RelativePath));
+        checkFileContentIntegrity(CtsDownloadService.FILE_INFO_2, fileUri);
+        checkDownloadResultAck(1);
+    }
+
+    public void testMultiFileDownloadFlow() throws Exception {
+        MbmsDownloadReceiverTest.AppIntentCapture captor =
+                new MbmsDownloadReceiverTest.AppIntentCapture(mContext, mHandler);
+        DownloadRequest request = new DownloadRequest.Builder(
+                CtsDownloadService.SOURCE_URI_3, destinationDirectoryUri)
+                .setServiceInfo(CtsDownloadService.FILE_SERVICE_INFO)
+                .setAppIntent(new Intent(MbmsDownloadReceiverTest.APP_INTENT_ACTION))
+                .build();
+
+        mDownloadSession.download(request);
+        mMiddlewareControl.actuallyStartDownloadFlow();
+
+        for (Intent i : captor.getIntents(2)) {
+            FileInfo fileInfo = i.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO);
+            Uri fileUri = null;
+            if (CtsDownloadService.FILE_INFO_1.equals(fileInfo)) {
+                fileUri = checkReceivedDownloadCompleteIntent(
+                        i, request, CtsDownloadService.FILE_INFO_1);
+            } else if (CtsDownloadService.FILE_INFO_2.equals(fileInfo)) {
+                fileUri = checkReceivedDownloadCompleteIntent(
+                        i, request, CtsDownloadService.FILE_INFO_2);
+            } else {
+                fail("Got unknown file info: " + fileInfo);
+            }
+            String relativePath = fileInfo.getUri().getPath().substring(
+                    CtsDownloadService.SOURCE_URI_3.getPath().length());
+            assertTrue("got path: " + fileUri.getPath() + ", should end with: " + relativePath,
+                    fileUri.getPath().endsWith(relativePath));
+            checkFileContentIntegrity(fileInfo, fileUri);
+        }
+        checkDownloadResultAck(2);
+    }
+
+
+    private Uri checkReceivedDownloadCompleteIntent(Intent downloadDoneIntent,
+            DownloadRequest downloadRequest, FileInfo expectedFileInfo) {
         assertEquals(MbmsDownloadReceiverTest.APP_INTENT_ACTION, downloadDoneIntent.getAction());
         assertEquals(MbmsDownloadSession.RESULT_SUCCESSFUL,
                 downloadDoneIntent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, -1));
-        assertEquals(testDownloadRequest,
+        assertEquals(downloadRequest,
                 downloadDoneIntent.getParcelableExtra(
                         MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST));
-        assertEquals(CtsDownloadService.FILE_INFO,
+        assertEquals(expectedFileInfo,
                 downloadDoneIntent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO));
-        Uri fileUri = downloadDoneIntent.getParcelableExtra(
+        return downloadDoneIntent.getParcelableExtra(
                 MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI);
-        InputStream is = mContext.getContentResolver().openInputStream(fileUri);
+    }
+
+    private void checkFileContentIntegrity(FileInfo fileInfo, Uri completedFileUri)
+            throws Exception {
+        assertEquals(fileInfo.getUri().getLastPathSegment(),
+                completedFileUri.getLastPathSegment());
+        InputStream is = mContext.getContentResolver().openInputStream(completedFileUri);
         byte[] contents = new byte[CtsDownloadService.SAMPLE_FILE_DATA.length];
         is.read(contents);
         for (int i = 0; i < contents.length; i++) {
             assertEquals(contents[i], CtsDownloadService.SAMPLE_FILE_DATA[i]);
         }
+    }
 
+    private void checkDownloadResultAck(int numAcks) throws Exception {
         List<Bundle> downloadResultAck =
                 getMiddlewareCalls(CtsDownloadService.METHOD_DOWNLOAD_RESULT_ACK);
-        assertEquals(1, downloadResultAck.size());
-        assertEquals(MbmsDownloadReceiver.RESULT_OK,
-                downloadResultAck.get(0).getInt(CtsDownloadService.ARGUMENT_RESULT_CODE, -1));
+        assertEquals(numAcks, downloadResultAck.size());
+        downloadResultAck.forEach((ack) -> assertEquals(MbmsDownloadReceiver.RESULT_OK,
+                ack.getInt(CtsDownloadService.ARGUMENT_RESULT_CODE, -1)));
     }
 }
diff --git a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadReceiverTest.java b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadReceiverTest.java
index 8232271..59d9a1b 100644
--- a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadReceiverTest.java
+++ b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadReceiverTest.java
@@ -65,15 +65,30 @@
         }
 
         public Intent getIntent() {
+            return getIntent(true);
+        }
+
+        public Intent getIntent(boolean unregister) {
             try {
                 Intent result = mReceivedIntent.poll(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS);
-                mContext.unregisterReceiver(mAppIntentReceiver);
+                if (unregister) {
+                    mContext.unregisterReceiver(mAppIntentReceiver);
+                }
                 return result;
             } catch (InterruptedException e) {
                 fail("test was interrupted");
                 return null;
             }
         }
+
+        public List<Intent> getIntents(int numExpected) {
+            ArrayList<Intent> result = new ArrayList<>(numExpected);
+            for (int i = 0; i < numExpected; i++) {
+                result.add(getIntent(false));
+            }
+            mContext.unregisterReceiver(mAppIntentReceiver);
+            return result;
+        }
     }
 
     private MbmsDownloadReceiver mReceiver;
@@ -165,7 +180,7 @@
         intentForReceiverTest.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST,
                 testDownloadRequest);
         intentForReceiverTest.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO,
-                CtsDownloadService.FILE_INFO);
+                CtsDownloadService.FILE_INFO_1);
         intentForReceiverTest.putExtra(VendorUtils.EXTRA_FINAL_URI,
                 Uri.fromFile(new File(new File(tempFileRootDir, TEST_SERVICE_ID), "file1")));
 
diff --git a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadSessionTest.java b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadSessionTest.java
index 5e70828..ac65030 100644
--- a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadSessionTest.java
+++ b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadSessionTest.java
@@ -151,14 +151,14 @@
 
     public void testGetDownloadStatus() throws Exception {
         DownloadRequest request = downloadRequestTemplate.build();
-        mDownloadSession.requestDownloadState(request, CtsDownloadService.FILE_INFO);
+        mDownloadSession.requestDownloadState(request, CtsDownloadService.FILE_INFO_1);
 
         List<Bundle> getDownloadStatusCalls =
                 getMiddlewareCalls(CtsDownloadService.METHOD_GET_DOWNLOAD_STATUS);
         assertEquals(1, getDownloadStatusCalls.size());
         assertEquals(request, getDownloadStatusCalls.get(0).getParcelable(
                 CtsDownloadService.ARGUMENT_DOWNLOAD_REQUEST));
-        assertEquals(CtsDownloadService.FILE_INFO, getDownloadStatusCalls.get(0).getParcelable(
+        assertEquals(CtsDownloadService.FILE_INFO_1, getDownloadStatusCalls.get(0).getParcelable(
                 CtsDownloadService.ARGUMENT_FILE_INFO));
     }
 
diff --git a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadStateCallbackTest.java b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadStateCallbackTest.java
deleted file mode 100644
index c01de7c..0000000
--- a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadStateCallbackTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telephony.embms.cts;
-
-import android.telephony.MbmsDownloadSession;
-import android.telephony.cts.embmstestapp.CtsDownloadService;
-import android.telephony.mbms.DownloadRequest;
-import android.telephony.mbms.DownloadStateCallback;
-import android.telephony.mbms.FileInfo;
-
-import com.android.internal.os.SomeArgs;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class MbmsDownloadStateCallbackTest extends MbmsDownloadTestBase {
-    private static final long SHORT_TIMEOUT = 500;
-
-    private class TestDSCallback extends DownloadStateCallback {
-        private final BlockingQueue<SomeArgs> mProgressUpdatedCalls = new LinkedBlockingQueue<>();
-        private final BlockingQueue<SomeArgs> mStateUpdatedCalls = new LinkedBlockingQueue<>();
-
-        public TestDSCallback(int filterFlags) {
-            super(filterFlags);
-        }
-
-        @Override
-        public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
-                currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
-                fullDecodedSize) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = request;
-            args.arg2 = fileInfo;
-            args.arg3 = currentDownloadSize;
-            args.arg4 = fullDownloadSize;
-            args.arg5 = currentDecodedSize;
-            args.arg6 = fullDecodedSize;
-            mProgressUpdatedCalls.add(args);
-        }
-
-        @Override
-        public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
-                @MbmsDownloadSession.DownloadStatus int state) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = request;
-            args.arg2 = fileInfo;
-            args.arg3 = state;
-            mStateUpdatedCalls.add(args);
-        }
-
-        public SomeArgs waitOnProgressUpdated(long timeout) {
-            try {
-                return mProgressUpdatedCalls.poll(timeout, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return null;
-            }
-        }
-
-        public SomeArgs waitOnStateUpdated(long timeout) {
-            try {
-                return mStateUpdatedCalls.poll(timeout, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return null;
-            }
-        }
-    }
-
-    public void testFullCallback() throws Exception {
-        int sampleInt = 10;
-        TestDSCallback callback = new TestDSCallback(DownloadStateCallback.ALL_UPDATES);
-        DownloadRequest request = downloadRequestTemplate.build();
-        mDownloadSession.registerStateCallback(request, mCallbackExecutor, callback);
-        mMiddlewareControl.fireOnProgressUpdated(request, CtsDownloadService.FILE_INFO,
-                sampleInt, sampleInt, sampleInt, sampleInt);
-        SomeArgs progressArgs = callback.waitOnProgressUpdated(ASYNC_TIMEOUT);
-        assertEquals(request, progressArgs.arg1);
-        assertEquals(CtsDownloadService.FILE_INFO, progressArgs.arg2);
-        assertEquals(sampleInt, progressArgs.arg3);
-        assertEquals(sampleInt, progressArgs.arg4);
-        assertEquals(sampleInt, progressArgs.arg5);
-        assertEquals(sampleInt, progressArgs.arg6);
-
-        mMiddlewareControl.fireOnStateUpdated(request, CtsDownloadService.FILE_INFO, sampleInt);
-        SomeArgs stateArgs = callback.waitOnStateUpdated(ASYNC_TIMEOUT);
-        assertEquals(request, stateArgs.arg1);
-        assertEquals(CtsDownloadService.FILE_INFO, stateArgs.arg2);
-        assertEquals(sampleInt, stateArgs.arg3);
-    }
-
-    public void testDeregistration() throws Exception {
-        TestDSCallback callback = new TestDSCallback(DownloadStateCallback.ALL_UPDATES);
-        DownloadRequest request = downloadRequestTemplate.build();
-        mDownloadSession.registerStateCallback(request, mCallbackExecutor, callback);
-        mDownloadSession.unregisterStateCallback(request, callback);
-
-        mMiddlewareControl.fireOnStateUpdated(null, null, 0);
-        assertNull(callback.waitOnStateUpdated(SHORT_TIMEOUT));
-        mMiddlewareControl.fireOnProgressUpdated(null, null, 0, 0, 0, 0);
-        assertNull(callback.waitOnProgressUpdated(SHORT_TIMEOUT));
-    }
-
-    public void testCallbackFiltering1() throws Exception {
-        TestDSCallback callback = new TestDSCallback(DownloadStateCallback.PROGRESS_UPDATES);
-        DownloadRequest request = downloadRequestTemplate.build();
-        mDownloadSession.registerStateCallback(request, mCallbackExecutor, callback);
-
-        mMiddlewareControl.fireOnStateUpdated(null, null, 0);
-        assertNull(callback.waitOnStateUpdated(SHORT_TIMEOUT));
-        mMiddlewareControl.fireOnProgressUpdated(null, null, 0, 0, 0, 0);
-        assertNotNull(callback.waitOnProgressUpdated(SHORT_TIMEOUT));
-    }
-
-    public void testCallbackFiltering2() throws Exception {
-        TestDSCallback callback = new TestDSCallback(DownloadStateCallback.STATE_UPDATES);
-        DownloadRequest request = downloadRequestTemplate.build();
-        mDownloadSession.registerStateCallback(request, mCallbackExecutor, callback);
-
-        mMiddlewareControl.fireOnStateUpdated(null, null, 0);
-        assertNotNull(callback.waitOnStateUpdated(SHORT_TIMEOUT));
-        mMiddlewareControl.fireOnProgressUpdated(null, null, 0, 0, 0, 0);
-        assertNull(callback.waitOnProgressUpdated(SHORT_TIMEOUT));
-    }
-}
diff --git a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadTestBase.java b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadTestBase.java
index bde9de6..2a27cb0 100644
--- a/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadTestBase.java
+++ b/tests/tests/telephony/src/android/telephony/embms/cts/MbmsDownloadTestBase.java
@@ -110,6 +110,7 @@
     }
 
     DownloadRequest.Builder downloadRequestTemplate;
+    Uri destinationDirectoryUri;
 
     Context mContext;
     HandlerThread mHandlerThread;
@@ -130,9 +131,9 @@
 
         File destinationDirectory = new File(mContext.getFilesDir(), "downloads");
         destinationDirectory.mkdirs();
-        Uri destinationDirectoryUri = Uri.fromFile(destinationDirectory);
+        destinationDirectoryUri = Uri.fromFile(destinationDirectory);
         downloadRequestTemplate = new DownloadRequest.Builder(
-                CtsDownloadService.DOWNLOAD_SOURCE_URI, destinationDirectoryUri)
+                CtsDownloadService.SOURCE_URI_1, destinationDirectoryUri)
                 .setServiceInfo(CtsDownloadService.FILE_SERVICE_INFO);
         getControlBinder();
         setupDownloadSession();
diff --git a/tests/video/Android.mk b/tests/video/Android.mk
index c70cfaf..176eddf 100644
--- a/tests/video/Android.mk
+++ b/tests/video/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctsmediautil compatibility-device-util ctstestrunner
 
-LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni libnativehelper_compat_libc++
+LOCAL_JNI_SHARED_LIBRARIES := libctscodecutils_jni libnativehelper_compat_libc++
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)