dynamic cts phase 1
 --add DynamicConfigPusher which push config file to certain location on device/host
 --DynamicConfig will parse config file and store it in a Map
 --Alter one host test case (SampleHostTests) to test dynamic config infrastructure. Modify its AndroidTest.xml to trigger DynamicConfigPusher.

Change-Id: I51eb6eeeaf344ade9f4cfe4225abbd5d04f78d49
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java b/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
new file mode 100644
index 0000000..303efec
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import android.os.Environment;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Load dynamic config for device side test cases
+ */
+public class DynamicConfigDeviceSide extends DynamicConfig {
+    private static String LOG_TAG = DynamicConfigDeviceSide.class.getSimpleName();
+
+    public DynamicConfigDeviceSide(String moduleName) throws XmlPullParserException, IOException {
+        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            throw new IOException("External storage is not mounted");
+        }
+        File configFile = getConfigFile(new File(CONFIG_FOLDER_ON_DEVICE), moduleName);
+        initConfigFromXml(configFile);
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildInfo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildInfo.java
index 12ce976..88446ee 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildInfo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildInfo.java
@@ -19,6 +19,8 @@
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * A simple {@link FolderBuildInfo} that uses a pre-existing Compatibility install.
@@ -31,6 +33,7 @@
     private final String mSuiteFullName;
     private final String mSuiteVersion;
     private final String mSuitePlan;
+    private Map<String, String> mConfigHashes = new HashMap<>();
 
     /**
      * Creates a {@link CompatibilityBuildInfo} containing information about the suite and
@@ -69,6 +72,14 @@
         return mSuitePlan;
     }
 
+    public void addDynamicConfig(String moduleName, String hash) {
+        mConfigHashes.put(moduleName, hash);
+    }
+
+    public Map<String, String> getDynamicConfigHashes() {
+        return mConfigHashes;
+    }
+
     /**
      * @return a {@link File} representing the "android-<suite>" folder of the Compatibility
      * installation
@@ -112,5 +123,4 @@
         }
         return testsDir;
     }
-
 }
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
new file mode 100644
index 0000000..748a202
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.targetprep;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildInfo;
+import com.android.compatibility.common.util.DynamicConfig;
+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.targetprep.BuildError;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Pushes dynamic config files from config repository
+ */
+@OptionClass(alias="dynamic-config-pusher")
+public class DynamicConfigPusher implements ITargetCleaner {
+
+    public enum TestTarget {
+        DEVICE,
+        HOST
+    }
+
+    private static final String LOG_TAG = DynamicConfigPusher.class.getSimpleName();
+
+    @Option(name = "cleanup", description = "Whether to clean up config files after test is done.")
+    private boolean mCleanup = true;
+
+    @Option(name="module-name", description = "Specify the module name")
+    private String mModuleName;
+
+    @Option(name = "target", description = "Specify the target, 'device' or 'host'")
+    private TestTarget mTarget;
+
+    private String mFilePushed;
+
+    void setModuleName(String moduleName) {
+        mModuleName = moduleName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError,
+            DeviceNotAvailableException {
+        File src = null;
+        CompatibilityBuildInfo build = (CompatibilityBuildInfo) buildInfo;
+        try {
+            src = DynamicConfig.getConfigFile(build.getTestsDir(), mModuleName);
+        } catch (FileNotFoundException e) {
+            throw new TargetSetupError(String.format(
+                    "Cannot find config file for module '%s' in repository", mModuleName));
+        }
+
+        if (mTarget == TestTarget.DEVICE) {
+            String dest = DynamicConfig.CONFIG_FOLDER_ON_DEVICE + src.getName();
+            if (!device.pushFile(src, dest)) {
+                throw new TargetSetupError(String.format("Failed to push local '%s' to remote '%s'",
+                        src.getName(), dest));
+            } else {
+                mFilePushed = dest;
+                build.addDynamicConfig(mModuleName, DynamicConfig.calculateSHA1(src));
+            }
+
+        } else if (mTarget == TestTarget.HOST) {
+            File storageDir = new File(DynamicConfig.CONFIG_FOLDER_ON_HOST);
+            if (!storageDir.exists()) storageDir.mkdir();
+            File dest = new File(DynamicConfig.CONFIG_FOLDER_ON_HOST + src.getName());
+            try {
+                FileUtil.copyFile(src, dest);
+            } catch (IOException e) {
+                throw new TargetSetupError(String.format("Failed to copy file from %s to %s",
+                        src.getAbsolutePath(), dest.getAbsolutePath()), e);
+            }
+            mFilePushed = dest.getAbsolutePath();
+            build.addDynamicConfig(mModuleName, DynamicConfig.calculateSHA1(src));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+            throws DeviceNotAvailableException {
+        if (mTarget == TestTarget.DEVICE) {
+            if (!(e instanceof DeviceNotAvailableException) && mCleanup && mFilePushed != null) {
+                device.executeShellCommand("rm -r " + mFilePushed);
+            }
+        } else if (mTarget == TestTarget.HOST) {
+            new File(mFilePushed).delete();
+        }
+    }
+}
\ No newline at end of file
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java
new file mode 100644
index 0000000..ac69034
--- /dev/null
+++ b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Load dynamic config for device side test cases
+ */
+public class DynamicConfigHostSide extends DynamicConfig {
+    private static String LOG_TAG = DynamicConfigHostSide.class.getSimpleName();
+
+    public DynamicConfigHostSide(String moduleName) throws IOException, XmlPullParserException {
+        File configFile = getConfigFile(new File(CONFIG_FOLDER_ON_HOST), moduleName);
+        initConfigFromXml(configFile);
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/DynamicConfig.java b/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
new file mode 100644
index 0000000..6c32c4a
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Load dynamic config for test cases
+ */
+public class DynamicConfig {
+    public final static String MODULE_NAME = "module-name";
+
+    //XML constant
+    private static final String NS = null;
+    private static final String DYNAMIC_CONFIG_TAG = "DynamicConfig";
+    private static final String CONFIG_TAG = "Config";
+    private static final String CONFIG_LIST_TAG = "ConfigList";
+    private static final String ITEM_TAG = "Item";
+    private static final String KEY_ATTR = "key";
+
+    public final static String CONFIG_FOLDER_ON_DEVICE = "/sdcard/dynamic-config-files/";
+    public final static String CONFIG_FOLDER_ON_HOST =
+            System.getProperty("java.io.tmpdir") + "/dynamic-config-files/";
+
+
+    protected Map<String, String> mDynamicParams;
+    protected Map<String, List<String>> mDynamicArrayParams;
+
+    protected void initConfigFromXml(File file) throws XmlPullParserException, IOException {
+        mDynamicParams = new HashMap<>();
+        mDynamicArrayParams = new HashMap<>();
+
+        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+        parser.setInput(new InputStreamReader(new FileInputStream(file)));
+
+        parser.nextTag();
+        parser.require(XmlPullParser.START_TAG, NS, DYNAMIC_CONFIG_TAG);
+
+        while (parser.nextTag() == XmlPullParser.START_TAG) {
+            if (parser.getName().equals(CONFIG_TAG)) {
+                String key = parser.getAttributeValue(NS, KEY_ATTR);
+                String value = parser.nextText();
+                parser.require(XmlPullParser.END_TAG, NS, CONFIG_TAG);
+                if (key != null && !key.isEmpty()) {
+                    mDynamicParams.put(key, value);
+                }
+            } else {
+                List<String> arrayValue = new ArrayList<>();
+                parser.require(XmlPullParser.START_TAG, NS, CONFIG_LIST_TAG);
+                String key = parser.getAttributeValue(NS, KEY_ATTR);
+                while (parser.nextTag() == XmlPullParser.START_TAG) {
+                    parser.require(XmlPullParser.START_TAG, NS, ITEM_TAG);
+                    arrayValue.add(parser.nextText());
+                    parser.require(XmlPullParser.END_TAG, NS, ITEM_TAG);
+                }
+                parser.require(XmlPullParser.END_TAG, NS, CONFIG_LIST_TAG);
+                if (key != null && !key.isEmpty()) {
+                    mDynamicArrayParams.put(key, arrayValue);
+                }
+            }
+        }
+        parser.require(XmlPullParser.END_TAG, NS, DYNAMIC_CONFIG_TAG);
+    }
+
+    public String getConfig(String key) {
+        return mDynamicParams.get(key);
+    }
+
+    public List<String> getConfigList(String key) {
+        return mDynamicArrayParams.get(key);
+    }
+
+    public static File getConfigFile(File configFolder, String moduleName) {
+        return new File(configFolder, String.format("%s.dynamic", moduleName));
+    }
+
+    public static String calculateSHA1(File file) {
+        MessageDigest sha1;
+        byte[] buffer = new byte[1024];
+        InputStream input;
+
+        try {
+            sha1 = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+            return null;
+        }
+        try {
+            input = new FileInputStream(file);
+            int length = input.read(buffer);
+            while (length != -1) {
+                sha1.update(buffer, 0, length);
+                length = input.read(buffer);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (byte b: sha1.digest()) {
+            sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
+        }
+        return sb.toString();
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/DynamicConfigTest.java b/common/util/tests/src/com/android/compatibility/common/util/DynamicConfigTest.java
new file mode 100644
index 0000000..60369ec
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/DynamicConfigTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import junit.framework.TestCase;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link DynamicConfig}
+ */
+public class DynamicConfigTest extends TestCase {
+    private static final String correctConfig =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+            "<DynamicConfig>\n" +
+            "    <Config key=\"test-config-1\">test config 1</Config>\n" +
+            "    <Config key=\"test-config-2\">testconfig2</Config>\n" +
+            "    <ConfigList key=\"config-list\">\n" +
+            "        <Item>config0</Item>\n" +
+            "        <Item>config1</Item>\n" +
+            "        <Item>config2</Item>\n" +
+            "        <Item>config3</Item>\n" +
+            "        <Item>config4</Item>\n" +
+            "    </ConfigList>\n" +
+            "    <ConfigList key=\"config-list-2\">\n" +
+            "        <Item>A</Item>\n" +
+            "        <Item>B</Item>\n" +
+            "        <Item>C</Item>\n" +
+            "        <Item>D</Item>\n" +
+            "        <Item>E</Item>\n" +
+            "    </ConfigList>\n" +
+            "</DynamicConfig>\n";
+
+    private static final String configWrongNodeName =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+            "<DynamicCsonfig>\n" +  //The node name DynamicConfig is intentionally mistyped
+            "    <Config key=\"test-config-1\">test config 1</Config>\n" +
+            "    <Config key=\"test-config-2\">testconfig2</Config>\n" +
+            "    <ConfigList key=\"config-list\">\n" +
+            "        <Item>Nevermore</Item>\n" +
+            "        <Item>Puck</Item>\n" +
+            "        <Item>Zeus</Item>\n" +
+            "        <Item>Earth Shaker</Item>\n" +
+            "        <Item>Vengeful Spirit</Item>\n" +
+            "    </ConfigList>\n" +
+            "    <ConfigList key=\"config-list-2\">\n" +
+            "        <Item>A</Item>\n" +
+            "        <Item>B</Item>\n" +
+            "        <Item>C</Item>\n" +
+            "        <Item>D</Item>\n" +
+            "        <Item>E</Item>\n" +
+            "    </ConfigList>\n" +
+            "</DynamicConfig>\n";
+
+    public void testCorrectConfig() throws Exception {
+        DynamicConfig config = new DynamicConfig();
+        File file = createFileFromStr(correctConfig);
+        config.initConfigFromXml(file);
+
+        assertEquals("Wrong Config", config.getConfig("test-config-1"), "test config 1");
+        assertEquals("Wrong Config", config.getConfig("test-config-2"), "testconfig2");
+        assertEquals("Wrong Config List", config.getConfigList("config-list").get(0), "config0");
+        assertEquals("Wrong Config List", config.getConfigList("config-list").get(2), "config2");
+        assertEquals("Wrong Config List", config.getConfigList("config-list-2").get(2), "C");
+    }
+
+    public void testConfigWithWrongNodeName() throws Exception {
+        DynamicConfig config = new DynamicConfig();
+        File file = createFileFromStr(configWrongNodeName);
+
+        try {
+            config.initConfigFromXml(file);
+            fail("Cannot detect error when config file has wrong node name");
+        } catch (XmlPullParserException e) {
+            //expected
+        }
+    }
+
+    private File createFileFromStr(String configStr) throws IOException {
+        File file = File.createTempFile("test", "dynamic");
+        FileOutputStream stream = new FileOutputStream(file);
+        stream.write(configStr.getBytes());
+        stream.flush();
+        return file;
+    }
+}
diff --git a/hostsidetests/sample/AndroidTest.xml b/hostsidetests/sample/AndroidTest.xml
index 50b44ec..534b5f5 100644
--- a/hostsidetests/sample/AndroidTest.xml
+++ b/hostsidetests/sample/AndroidTest.xml
@@ -18,4 +18,8 @@
     <option name="apk-installer:test-file-name" value="CtsSampleDeviceApp.apk" />
     <test class="android.sample.cts.SampleHostTest" />
     <test class="android.sample.cts.SampleHostResultTest" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="module-name" value="CtsSampleHostTestCases"/>
+    </target_preparer>
 </configuration>
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
index f276712..ad54e3b 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
@@ -16,6 +16,7 @@
 
 package android.sample.cts;
 
+import com.android.compatibility.common.util.DynamicConfigHostSide;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceTestCase;
 
@@ -52,6 +53,15 @@
     private static final String TEST_STRING = "SampleTestString";
 
     /**
+     * Test if dynamic config on the host side works
+     * @throws Exception
+     */
+    public void testDynamicConfig() throws Exception {
+        DynamicConfigHostSide config = new DynamicConfigHostSide("CtsSampleHostTestCases");
+        assertEquals(config.getConfig("host-config"), "host-config-val");
+    }
+
+    /**
      * Tests the string was successfully logged to Logcat from the activity.
      *
      * @throws Exception
diff --git a/run_unit_tests.sh b/run_unit_tests.sh
index 2937133..a7c058f 100755
--- a/run_unit_tests.sh
+++ b/run_unit_tests.sh
@@ -75,6 +75,7 @@
     com.android.compatibility.common.tradefed.testtype.ModuleRepoTest\
     com.android.compatibility.common.util.AbiUtilsTest\
     com.android.compatibility.common.util.CaseResultTest\
+    com.android.compatibility.common.util.DynamicConfigTest\
     com.android.compatibility.common.util.MetricsStoreTest\
     com.android.compatibility.common.util.MetricsXmlSerializerTest\
     com.android.compatibility.common.util.ModuleResultTest\
diff --git a/tests/sample/AndroidManifest.xml b/tests/sample/AndroidManifest.xml
index 194c904..2f32e86 100755
--- a/tests/sample/AndroidManifest.xml
+++ b/tests/sample/AndroidManifest.xml
@@ -18,6 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.sample.cts">
 
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.sample.SampleDeviceActivity" >
diff --git a/tests/sample/AndroidTest.xml b/tests/sample/AndroidTest.xml
index f2ec348..3fb350d 100644
--- a/tests/sample/AndroidTest.xml
+++ b/tests/sample/AndroidTest.xml
@@ -20,4 +20,8 @@
         <option name="package" value="android.sample.cts" />
         <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
     </test>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="module-name" value="CtsSampleDeviceTestCases"/>
+    </target_preparer>
 </configuration>
diff --git a/tests/sample/src/android/sample/cts/SampleDeviceTest.java b/tests/sample/src/android/sample/cts/SampleDeviceTest.java
index 13b7ea6..e8a8e27 100644
--- a/tests/sample/src/android/sample/cts/SampleDeviceTest.java
+++ b/tests/sample/src/android/sample/cts/SampleDeviceTest.java
@@ -18,6 +18,8 @@
 import android.sample.SampleDeviceActivity;
 import android.test.ActivityInstrumentationTestCase2;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
 /**
  * A simple compatibility test which tests the SharedPreferences API.
  *
@@ -73,4 +75,13 @@
         mActivity.clearPreferences();
         assertNull("Preferences were not cleared", mActivity.getPreference(KEY));
     }
+
+    /**
+     * Test if Dynamic Config on the device side works
+     * @throws Exception
+     */
+    public void testDynamicConfig() throws Exception {
+        DynamicConfigDeviceSide config = new DynamicConfigDeviceSide("CtsSampleDeviceTestCases");
+        assertEquals(config.getConfig("device-config"), "device-config-val");
+    }
 }