Merge "Increase timeout for ordered broadcast response." into nougat-cts-dev
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 68aefa9..0c1e1db 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1938,6 +1938,17 @@
                 android:value="android.software.live_tv" />
         </activity>
 
+        <activity android:name=".tv.MicrophoneDeviceTestActivity"
+                  android:label="@string/tv_microphone_device_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tv" />
+            <meta-data android:name="test_required_features"
+                       android:value="android.software.leanback" />
+        </activity>
+
         <activity android:name=".screenpinning.ScreenPinningTestActivity"
             android:label="@string/screen_pinning_test">
             <intent-filter>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 91ca7c7..5c5f56d 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2527,6 +2527,8 @@
 
     <string name="tv_parental_control_turn_off_disabled">No</string>
     <string name="tv_parental_control_turn_off_enabled">Yes</string>
+    <string name="tv_yes">Yes</string>
+    <string name="tv_no">No</string>
     <string name="tv_launch_tv_app">Launch TV app</string>
     <string name="tv_launch_epg">Launch EPG</string>
     <string name="tv_launch_setup">Launch setup</string>
@@ -2609,6 +2611,35 @@
     2) You should see the text \"Cts App-Link Text\".\n
     </string>
 
+    <string name="tv_microphone_device_test">Microphone device test</string>
+    <string name="tv_microphone_device_test_info">
+    This test checks if InputDevice.hasMicrophone of the Media API reports a
+    correct value on every input device (including remote controls).
+    </string>
+    <string name="tv_microphone_device_test_prep_question">
+    Before continuing, please make sure that you pair all primary input
+    devices intended to be used by the device, including bluetooth
+    remotes and companion remote-control apps such as the Android TV Remote Control.
+    Have you paired every primary input device with the device under test (DUT)?
+    </string>
+    <string name="tv_microphone_device_test_mic_question">
+    Does input device \"%1$s\" have a microphone?
+    </string>
+    <string name="tv_microphone_device_test_negative_mismatch">
+    InputDevice.hasMicrophone reports that this input device DOES have a
+    microphone whereas you selected that it does not.  Please correct it by
+    declaring \"audio.mic = 0\" in the device\'s input device configuration
+    (.idc) file.
+    </string>
+    <string name="tv_microphone_device_test_positive_mismatch">
+    InputDevice.hasMicrophone reports that this input device does NOT have a
+    microphone whereas you selected that it does.  Please correct it by
+    declaring \"audio.mic = 1\" in the device\'s input device configuration
+    (.idc) file and make sure that relevant files such as key layout (.kl)
+    and key character map (.kcm) files are found by the system accordingly.
+    </string>
+    <string name="tv_microphone_device_test_no_input_devices">No input devices found.</string>
+
     <string name="overlay_view_text">Overlay View Dummy Text</string>
     <string name="custom_rating">Example of input app specific custom rating.</string>
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MicrophoneDeviceTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MicrophoneDeviceTestActivity.java
new file mode 100644
index 0000000..eca02c1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MicrophoneDeviceTestActivity.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.tv;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.View;
+import android.widget.Toast;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Tests for verifying that all input devices report correct hasMicrophone() states.
+ */
+@SuppressLint("NewApi")
+public class MicrophoneDeviceTestActivity extends TvAppVerifierActivity
+        implements View.OnClickListener {
+    private static final String TAG = "MicrophoneDeviceTestActivity";
+    private static final boolean PASS = true;
+
+    private InputManager mInputManager;
+    private HashMap<View, List<Object>> mInputDeviceItems;
+    private View mPreparationYesItem;
+    private View mPreparationNoItem;
+
+    @Override
+    public void onClick(View v) {
+        final View postTarget = getPostTarget();
+
+        if (containsButton(mPreparationYesItem, v)) {
+            setPassState(mPreparationYesItem, true);
+            setButtonEnabled(mPreparationNoItem, false);
+            createInputDeviceItems();
+            return;
+        } else if (containsButton(mPreparationNoItem, v)) {
+            setPassState(mPreparationYesItem, false);
+            setButtonEnabled(mPreparationNoItem, false);
+            getPassButton().setEnabled(false);
+            return;
+        } else if (mInputDeviceItems == null) {
+            return;
+        }
+
+        for (View item : mInputDeviceItems.keySet()) {
+            if (containsButton(item, v)) {
+                final List<Object> triple = mInputDeviceItems.get(item);
+                final boolean userAnswer = (boolean) triple.get(0);
+                final boolean actualAnswer = (boolean) triple.get(1);
+                final View pairedItem = (View) triple.get(2);
+
+                if (userAnswer == actualAnswer) {
+                    setPassState(userAnswer ? item : pairedItem, true);
+                    setButtonEnabled(userAnswer ? pairedItem : item, false);
+                    item.setTag(PASS); pairedItem.setTag(PASS);
+                    if (checkAllPassed()) {
+                        getPassButton().setEnabled(true);
+                    }
+                    return;
+                }
+
+                final int messageId =
+                    actualAnswer ? R.string.tv_microphone_device_test_negative_mismatch :
+                    R.string.tv_microphone_device_test_positive_mismatch;
+                Toast.makeText(this, messageId, Toast.LENGTH_LONG).show();
+                setPassState(userAnswer ? item : pairedItem, false);
+                getPassButton().setEnabled(false);
+                return;
+            }
+        }
+    }
+
+    @Override
+    protected void createTestItems() {
+        mPreparationYesItem = createUserItem(
+                R.string.tv_microphone_device_test_prep_question,
+                R.string.tv_yes, this);
+        setButtonEnabled(mPreparationYesItem, true);
+        mPreparationNoItem = createButtonItem(R.string.tv_no, this);
+        setButtonEnabled(mPreparationNoItem, true);
+    }
+
+    private void createInputDeviceItems() {
+        final Context context = MicrophoneDeviceTestActivity.this;
+        mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
+
+        final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+        mInputDeviceItems = new HashMap<View, List<Object>>();
+
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+            final boolean hasMicrophone = inputDevice.hasMicrophone();
+            Log.w(TAG, "name: " + inputDevice.getName() + ", mic: " + hasMicrophone + ", virtual: "
+                  + inputDevice.isVirtual() + ", descriptor: " + inputDevice.getDescriptor());
+
+            // Skip virtual input devices such as virtual keyboards.  This does
+            // not, e.g., include com.google.android.tv.remote bluetooth connections.
+            if (inputDevice.isVirtual()) {
+                continue;
+            }
+
+            final CharSequence micQuestion =
+                getString(R.string.tv_microphone_device_test_mic_question, inputDevice.getName());
+
+            final View inputDeviceYesItem = createUserItem(micQuestion, R.string.tv_yes, this);
+            setButtonEnabled(inputDeviceYesItem, true);
+            final View inputDeviceNoItem = createButtonItem(R.string.tv_no, this);
+            setButtonEnabled(inputDeviceNoItem, true);
+            mInputDeviceItems.put(
+                inputDeviceYesItem, Arrays.asList(true, hasMicrophone, inputDeviceNoItem));
+            mInputDeviceItems.put(
+                inputDeviceNoItem, Arrays.asList(false, hasMicrophone, inputDeviceYesItem));
+        }
+
+        if (mInputDeviceItems.size() == 0) {
+            Toast.makeText(this, R.string.tv_microphone_device_test_no_input_devices,
+                           Toast.LENGTH_LONG).show();
+            getPassButton().setEnabled(true);
+        }
+    }
+
+    @Override
+    protected void setInfoResources() {
+        setInfoResources(R.string.tv_microphone_device_test,
+                R.string.tv_microphone_device_test_info, -1);
+    }
+
+    private boolean hasPassed(View item) {
+        return (item.getTag() != null) && ((Boolean) item.getTag()) == PASS;
+    }
+
+    private boolean checkAllPassed() {
+        if (mInputDeviceItems != null && mInputDeviceItems.size() > 0) {
+            for (View item : mInputDeviceItems.keySet()) {
+                if (!hasPassed(item)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
index cb50fac..b95ba98 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
@@ -93,6 +93,22 @@
     }
 
     /**
+     * Call this to create a test step where the user must perform some action.
+     */
+    protected View createUserItem(CharSequence instructionCharSequence,
+                                  int buttonTextId, View.OnClickListener l) {
+        View item = mInflater.inflate(R.layout.tv_item, mItemList, false);
+        TextView instructions = (TextView) item.findViewById(R.id.instructions);
+        instructions.setText(instructionCharSequence);
+        Button button = (Button) item.findViewById(R.id.user_action_button);
+        button.setVisibility(View.VISIBLE);
+        button.setText(buttonTextId);
+        button.setOnClickListener(l);
+        mItemList.addView(item);
+        return item;
+    }
+
+    /**
      * Call this to create a test step where the test automatically evaluates whether
      * an expected condition is satisfied.
      */
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
index acdaa0a..4cc0351 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
@@ -157,6 +157,9 @@
     public void addDynamicConfigFile(String moduleName, File configFile) {
         mBuildInfo.addBuildAttribute(DynamicConfigHostSide.CONFIG_PATH_PREFIX + moduleName,
                 configFile.getAbsolutePath());
+        // If invocation fails and ResultReporter never moves this file into the result,
+        // using setFile() ensures BuildInfo will delete upon cleanUp().
+        mBuildInfo.setFile(configFile.getName(), configFile, "1" /* version */);
     }
 
     public void setModuleIds(String[] moduleIds) {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index 32d3cda..fecc216 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -483,7 +483,7 @@
         long startTime = mResult.getStartTime();
         try {
             // Zip the full test results directory.
-            copyDynamicConfigFiles(mBuildHelper.getDynamicConfigFiles(), mResultDir);
+            copyDynamicConfigFiles();
             copyFormattingFiles(mResultDir, mBuildHelper.getSuiteName());
 
             File resultFile = ResultHandler.writeResults(mBuildHelper.getSuiteName(),
@@ -702,18 +702,32 @@
      * @param configFiles
      * @param resultsDir
      */
-    static void copyDynamicConfigFiles(Map<String, File> configFiles, File resultsDir) {
-        if (configFiles.size() == 0) return;
+    private void copyDynamicConfigFiles() {
+        File configDir = new File(mResultDir, "config");
+        if (!configDir.mkdir()) {
+            warn("Failed to make dynamic config directory \"%s\" in the result",
+                    configDir.getAbsolutePath());
+        }
 
-        File folder = new File(resultsDir, "config");
-        folder.mkdir();
-        for (String moduleName : configFiles.keySet()) {
-            File resultFile = new File(folder, moduleName+".dynamic");
-            try {
-                FileUtil.copyFile(configFiles.get(moduleName), resultFile);
-                FileUtil.deleteFile(configFiles.get(moduleName));
-            } catch (IOException e) {
-                warn("Failed to copy config file for %s to file", moduleName);
+        Set<String> uniqueModules = new HashSet<>();
+        for (IBuildInfo buildInfo : mMasterBuildInfos) {
+            CompatibilityBuildHelper helper = new CompatibilityBuildHelper(buildInfo);
+            Map<String, File> dcFiles = helper.getDynamicConfigFiles();
+            for (String moduleName : dcFiles.keySet()) {
+                File srcFile = dcFiles.get(moduleName);
+                if (!uniqueModules.contains(moduleName)) {
+                    // have not seen config for this module yet, copy into result
+                    File destFile = new File(configDir, moduleName + ".dynamic");
+                    try {
+                        FileUtil.copyFile(srcFile, destFile);
+                        uniqueModules.add(moduleName); // Add to uniqueModules if copy succeeds
+                    } catch (IOException e) {
+                        warn("Failure when copying config file \"%s\" to \"%s\" for module %s",
+                                srcFile.getAbsolutePath(), destFile.getAbsolutePath(), moduleName);
+                        CLog.e(e);
+                    }
+                }
+                FileUtil.deleteFile(srcFile);
             }
         }
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
index 7600eb7..2570432 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
@@ -28,7 +28,6 @@
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.ITargetCleaner;
 import com.android.tradefed.targetprep.TargetSetupError;
-import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.StreamUtil;
 
 import org.json.JSONException;
@@ -50,7 +49,6 @@
     }
 
     private static final String LOG_TAG = DynamicConfigPusher.class.getSimpleName();
-    private static final String TMP_FOLDER_DYNAMIC_FILES = "dynamic-config-files";
 
     @Option(name = "cleanup", description = "Whether to remove config files from the test " +
             "target after test completion.")
@@ -68,8 +66,7 @@
             "from the server, e.g. \"1.0\". Defaults to suite version string.")
     private static String mVersion;
 
-
-    private String mFilePushed;
+    private String mDeviceFilePushed;
 
     void setModuleName(String moduleName) {
         mModuleName = moduleName;
@@ -115,46 +112,27 @@
                     "Dynamic config override URL is not set, using local configuration values");
         }
 
-        File src = null;
+        // Use DynamicConfigHandler to merge local and service configuration into one file
+        File hostFile = null;
         try {
-            src = DynamicConfigHandler.getMergedDynamicConfigFile(
+            hostFile = DynamicConfigHandler.getMergedDynamicConfigFile(
                     localConfigFile, apfeConfigInJson, mModuleName);
         } catch (IOException | XmlPullParserException | JSONException e) {
             throw new TargetSetupError("Cannot get merged dynamic config file", e);
         }
 
-        switch (mTarget) {
-            case DEVICE:
-                String deviceDest = DynamicConfig.CONFIG_FOLDER_ON_DEVICE + src.getName();
-                if (!device.pushFile(src, deviceDest)) {
-                    throw new TargetSetupError(String.format(
-                            "Failed to push local '%s' to remote '%s'",
-                            src.getAbsolutePath(), deviceDest));
-                } else {
-                    mFilePushed = deviceDest;
-                    buildHelper.addDynamicConfigFile(mModuleName, src);
-                }
-                break;
-
-            case HOST:
-                File storageDir = null;
-                try {
-                    storageDir = FileUtil.createTempDir(TMP_FOLDER_DYNAMIC_FILES);
-                } catch (IOException e) {
-                    throw new TargetSetupError("Fail to create a tmp folder for dynamic config "
-                            + "files", e);
-                }
-                File hostDest = new File(storageDir, src.getName());
-                try {
-                    FileUtil.copyFile(src, hostDest);
-                } catch (IOException e) {
-                    throw new TargetSetupError(String.format("Failed to copy file from %s to %s",
-                            src.getAbsolutePath(), hostDest.getAbsolutePath()), e);
-                }
-                mFilePushed = storageDir.getAbsolutePath();
-                buildHelper.addDynamicConfigFile(mModuleName, src);
-                break;
+        if (TestTarget.DEVICE.equals(mTarget)) {
+            String deviceDest = String.format("%s%s.dynamic",
+                    DynamicConfig.CONFIG_FOLDER_ON_DEVICE, mModuleName);
+            if (!device.pushFile(hostFile, deviceDest)) {
+                throw new TargetSetupError(String.format(
+                        "Failed to push local '%s' to remote '%s'", hostFile.getAbsolutePath(),
+                        deviceDest));
+            }
+            mDeviceFilePushed = deviceDest;
         }
+        // add host file to build
+        buildHelper.addDynamicConfigFile(mModuleName, hostFile);
     }
 
     /**
@@ -163,16 +141,10 @@
     @Override
     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
             throws DeviceNotAvailableException {
-        switch (mTarget) {
-            case DEVICE:
-                if (!(e instanceof DeviceNotAvailableException)
-                        && mCleanup && mFilePushed != null) {
-                    device.executeShellCommand("rm -r " + mFilePushed);
-                }
-                break;
-            case HOST:
-                FileUtil.recursiveDelete(new File(mFilePushed));
-                break;
+        // Remove any file we have pushed to the device, host file will be moved to the result
+        // directory by ResultReporter upon invocation completion.
+        if (mDeviceFilePushed != null && !(e instanceof DeviceNotAvailableException) && mCleanup) {
+            device.executeShellCommand("rm -r " + mDeviceFilePushed);
         }
     }
 }
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java
index 8df1ebc..0528a1b 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java
@@ -37,7 +37,8 @@
 
 public class DynamicConfigHandler {
 
-    private final static String MERGED_CONFIG_FILE_FOLDER = "dynamic-config-files-merged";
+    private final static String MERGED_CONFIG_FILE = "dynamic-config-files-merged";
+    private final static String FILE_EXT = ".dynamic";
     private static final String NS = null; //xml constant representing null namespace
     private static final String ENCODING = "UTF-8";
 
@@ -76,8 +77,7 @@
     private static File storeMergedConfigFile(Map<String, List<String>> configMap,
             String moduleName) throws XmlPullParserException, IOException {
 
-        File folder = FileUtil.createTempDir(MERGED_CONFIG_FILE_FOLDER);
-        File mergedConfigFile = new File(folder, moduleName + ".dynamic");
+        File mergedConfigFile = FileUtil.createTempFile(MERGED_CONFIG_FILE, FILE_EXT);
         OutputStream stream = new FileOutputStream(mergedConfigFile);
         XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
         serializer.setOutput(stream, ENCODING);
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/ReportLogHostInfoStore.java b/common/host-side/util/src/com/android/compatibility/common/util/ReportLogHostInfoStore.java
index 38b742b..fecccf4 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/ReportLogHostInfoStore.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/ReportLogHostInfoStore.java
@@ -16,6 +16,7 @@
 package com.android.compatibility.common.util;
 
 import com.android.json.stream.JsonWriter;
+import com.android.tradefed.util.FileUtil;
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
@@ -92,6 +93,8 @@
                 // Copy from temp file directly to avoid large metrics string in memory.
                 metricsWriter.write(line, 0, line.length());
             }
+        } finally {
+            FileUtil.deleteFile(tempJsonFile);
         }
     }
 }
diff --git a/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java b/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java
index 36c4970..204a8f1 100644
--- a/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java
+++ b/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.compatibility.common.util;
 
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.StreamUtil;
+
 import junit.framework.TestCase;
 
 import java.io.File;
@@ -92,26 +95,31 @@
     public void testDynamicConfigHandler() throws Exception {
         String module = "test1";
         File localConfigFile = createFileFromStr(localConfig, module);
+        File mergedFile = null;
+        try {
+            mergedFile = DynamicConfigHandler
+                    .getMergedDynamicConfigFile(localConfigFile, overrideJson, module);
 
-        File mergedFile = DynamicConfigHandler
-                .getMergedDynamicConfigFile(localConfigFile, overrideJson, module);
+            Map<String, List<String>> configMap = DynamicConfig.createConfigMap(mergedFile);
 
-        Map<String, List<String>> configMap = DynamicConfig.createConfigMap(mergedFile);
+            assertEquals("override-config-val-1", configMap.get("override-config-1").get(0));
+            assertTrue(configMap.get("override-config-list-1")
+                    .contains("override-config-list-val-1-1"));
+            assertTrue(configMap.get("override-config-list-1")
+                    .contains("override-config-list-val-1-2"));
+            assertTrue(configMap.get("override-config-list-3").size() == 0);
 
-        assertEquals("override-config-val-1", configMap.get("override-config-1").get(0));
-        assertTrue(configMap.get("override-config-list-1")
-                .contains("override-config-list-val-1-1"));
-        assertTrue(configMap.get("override-config-list-1")
-                .contains("override-config-list-val-1-2"));
-        assertTrue(configMap.get("override-config-list-3").size() == 0);
+            assertEquals("test config 1", configMap.get("test-config-1").get(0));
+            assertTrue(configMap.get("config-list").contains("config2"));
 
-        assertEquals("test config 1", configMap.get("test-config-1").get(0));
-        assertTrue(configMap.get("config-list").contains("config2"));
-
-        assertEquals("override-config-val-2", configMap.get("override-config-2").get(0));
-        assertEquals(1, configMap.get("override-config-list-2").size());
-        assertTrue(configMap.get("override-config-list-2")
-                .contains("override-config-list-val-2-1"));
+            assertEquals("override-config-val-2", configMap.get("override-config-2").get(0));
+            assertEquals(1, configMap.get("override-config-list-2").size());
+            assertTrue(configMap.get("override-config-list-2")
+                    .contains("override-config-list-val-2-1"));
+        } finally {
+            FileUtil.deleteFile(localConfigFile);
+            FileUtil.recursiveDelete(mergedFile);
+        }
     }
 
 
@@ -123,9 +131,7 @@
             stream.write(configStr.getBytes());
             stream.flush();
         } finally {
-            if (stream != null) {
-                stream.close();
-            }
+            StreamUtil.close(stream);
         }
         return file;
     }
diff --git a/common/util/src/com/android/compatibility/common/util/ModuleResult.java b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
index 60500a2..e786a1e 100644
--- a/common/util/src/com/android/compatibility/common/util/ModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
@@ -267,11 +267,14 @@
 
         this.mRuntime += otherModuleResult.getRuntime();
         this.mNotExecuted += otherModuleResult.getNotExecuted();
-        this.setDone(otherModuleResult.isDoneSoFar());
-        this.mActualTestRuns += otherModuleResult.getTestRuns();
-        // expected test runs are the same across shards, except for shards that do not run this
-        // module at least once (for which the value is not yet set).
-        this.mExpectedTestRuns = otherModuleResult.getExpectedTestRuns();
+        // only touch variables related to 'done' status if this module is not already done
+        if (!isDone()) {
+            this.setDone(otherModuleResult.isDoneSoFar());
+            this.mActualTestRuns += otherModuleResult.getTestRuns();
+            // expected test runs are the same across shards, except for shards that do not run
+            // this module at least once (for which the value is not yet set).
+            this.mExpectedTestRuns = otherModuleResult.getExpectedTestRuns();
+        }
         for (ICaseResult otherCaseResult : otherModuleResult.getResults()) {
             ICaseResult caseResult = getOrCreateResult(otherCaseResult.getName());
             caseResult.mergeFrom(otherCaseResult);
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index b1455bd..eb83341 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -681,8 +681,10 @@
             //
             // Verify recording starts within 400 ms of AudioTrack completion (typical 180ms)
             // Verify recording completes within 50 ms of expected test time (typical 20ms)
-            assertEquals(TEST_NAME, PLAYBACK_TIME_IN_MS, firstSampleTime - startTime, 400);
-            assertEquals(TEST_NAME, RECORD_TIME_IN_MS, endTime - firstSampleTime, 50);
+            assertEquals(TEST_NAME, PLAYBACK_TIME_IN_MS, firstSampleTime - startTime,
+                isLowLatencyDevice() ? 400 : 800);
+            assertEquals(TEST_NAME, RECORD_TIME_IN_MS, endTime - firstSampleTime,
+                isLowLatencyDevice()? 50 : 400);
 
             record.stop();
             assertEquals(TEST_NAME, AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
@@ -984,7 +986,8 @@
             assertTrue(coldInputStartTime < 5000); // must start within 5 seconds.
 
             // Verify recording completes within 50 ms of expected test time (typical 20ms)
-            assertEquals(TEST_TIME_MS, endTime - firstSampleTime, auditRecording ? 1000 : 50);
+            assertEquals(TEST_TIME_MS, endTime - firstSampleTime, auditRecording ?
+                (isLowLatencyDevice() ? 1000 : 2000) : (isLowLatencyDevice() ? 50 : 400));
 
             // Even though we've read all the frames we want, the events may not be sent to
             // the listeners (events are handled through a separate internal callback thread).
@@ -1065,16 +1068,18 @@
         //        " markerPeriodsReceived " + markerList.size());
         //Log.d(TAG, "updatePeriods " + updatePeriods +
         //        " updatePeriodsReceived " + periodicList.size());
-        assertTrue(TAG + ": markerPeriods " + markerPeriods +
-                " <= markerPeriodsReceived " + markerList.size() +
-                " <= markerPeriodsMax " + markerPeriodsMax,
-                markerPeriods <= markerList.size()
-                && markerList.size() <= markerPeriodsMax);
-        assertTrue(TAG + ": updatePeriods " + updatePeriods +
-               " <= updatePeriodsReceived " + periodicList.size() +
-               " <= updatePeriodsMax " + updatePeriodsMax,
-                updatePeriods <= periodicList.size()
-                && periodicList.size() <= updatePeriodsMax);
+        if (isLowLatencyDevice()) {
+            assertTrue(TAG + ": markerPeriods " + markerPeriods +
+                    " <= markerPeriodsReceived " + markerList.size() +
+                    " <= markerPeriodsMax " + markerPeriodsMax,
+                    markerPeriods <= markerList.size()
+                    && markerList.size() <= markerPeriodsMax);
+            assertTrue(TAG + ": updatePeriods " + updatePeriods +
+                   " <= updatePeriodsReceived " + periodicList.size() +
+                   " <= updatePeriodsMax " + updatePeriodsMax,
+                    updatePeriods <= periodicList.size()
+                    && periodicList.size() <= updatePeriodsMax);
+        }
 
         // Since we don't have accurate positioning of the start time of the recorder,
         // and there is no record.getPosition(), we consider only differential timing
@@ -1092,7 +1097,9 @@
             //Log.d(TAG, "Marker: " + i + " expected(" + expected + ")  actual(" + actual
             //        + ")  diff(" + (actual - expected) + ")"
             //        + " tolerance " + toleranceInFrames);
-            assertEquals(expected, actual, toleranceInFrames);
+            if (isLowLatencyDevice()) {
+                assertEquals(expected, actual, toleranceInFrames);
+            }
             markerStat.add((double)(actual - expected) * 1000 / TEST_SR);
         }
 
@@ -1106,7 +1113,9 @@
             //Log.d(TAG, "Update: " + i + " expected(" + expected + ")  actual(" + actual
             //        + ")  diff(" + (actual - expected) + ")"
             //        + " tolerance " + toleranceInFrames);
-            assertEquals(expected, actual, toleranceInFrames);
+            if (isLowLatencyDevice()) {
+                assertEquals(expected, actual, toleranceInFrames);
+            }
             periodicStat.add((double)(actual - expected) * 1000 / TEST_SR);
         }
 
@@ -1234,6 +1243,11 @@
                 .isLowRamDevice();
     }
 
+    private boolean isLowLatencyDevice() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUDIO_LOW_LATENCY);
+    }
+
     private void verifyContinuousTimestamps(
             AudioTimestamp startTs, AudioTimestamp stopTs, int sampleRate)
             throws Exception {
@@ -1246,7 +1260,7 @@
         // Usually the ratio is accurate to one part per thousand or better.
         // Log.d(TAG, "ratio=" + ratio + ", timeDiff=" + timeDiff + ", frameDiff=" + frameDiff +
         //        ", timeByFrames=" + timeByFrames + ", sampleRate=" + sampleRate);
-        assertEquals(1.0 /* expected */, ratio, 0.01 /* delta */);
+        assertEquals(1.0 /* expected */, ratio, isLowLatencyDevice() ? 0.01 : 0.5 /* delta */);
     }
 
     // remove if AudioTimestamp has a better toString().
diff --git a/tests/tests/security/src/android/security/cts/EncryptionTest.java b/tests/tests/security/src/android/security/cts/EncryptionTest.java
index 85d82e2..2f18866 100644
--- a/tests/tests/security/src/android/security/cts/EncryptionTest.java
+++ b/tests/tests/security/src/android/security/cts/EncryptionTest.java
@@ -22,6 +22,7 @@
 import junit.framework.TestCase;
 
 import android.app.ActivityManager;
+import android.content.pm.PackageManager;
 import android.content.Context;
 import android.util.Log;
 import java.io.BufferedReader;
@@ -79,7 +80,7 @@
 
     private boolean isRequired() {
         // Optional before MIN_API_LEVEL or if the device has low RAM
-        return PropertyUtil.getFirstApiLevel() >= MIN_API_LEVEL && !hasLowRAM();
+        return PropertyUtil.getFirstApiLevel() >= MIN_API_LEVEL && !hasLowRAM() && !isTelevision();
     }
 
     public void testConfig() throws Exception {
@@ -105,6 +106,12 @@
         }
     }
 
+    private boolean isTelevision() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
+                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
     public void testEncryption() throws Exception {
         if (!isRequired() || deviceIsEncrypted()) {
             return;