Merge "Improve CTS coverage for pointer capture API"
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index ce1acbc..9fa9a7c 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -72,6 +72,8 @@
             android:largeHeap="true"
             android:theme="@android:style/Theme.DeviceDefault">
 
+        <meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
+
         <meta-data android:name="com.google.android.backup.api_key"
                 android:value="AEdPqrEAAAAIbK6ldcOzoeRtQ1u1dFVJ1A7KetRhit-a1Xa82Q" />
         <meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
@@ -147,14 +149,15 @@
                     android:value="android.software.backup" />
         </activity>
 
-        <activity android:name=".backup.BackupAccessibilityTestActivity" android:label="@string/backup_accessibility_test">
+	<!-- Further work is required for this test, b/32798562  -->
+        <!-- activity android:name=".backup.BackupAccessibilityTestActivity" android:label="@string/backup_accessibility_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_required_features"
                     android:value="android.software.backup" />
-        </activity>
+        </activity -->
 
 <!--            CTS Verifier Bluetooth Test Top Screen -->
         <activity
@@ -2721,6 +2724,46 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
             </activity>
 
+        <activity android:name=".audio.USBAudioPeripheralAttributesActivity"
+                  android:label="@string/audio_uap_attribs_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_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+        </activity>
+
+        <activity android:name=".audio.USBAudioPeripheralPlayActivity"
+                  android:label="@string/audio_uap_play_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_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+        </activity>
+
+        <activity android:name=".audio.USBAudioPeripheralRecordActivity"
+                  android:label="@string/audio_uap_record_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_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+        </activity>
+
+        <activity android:name=".audio.USBAudioPeripheralButtonsActivity"
+                  android:label="@string/audio_uap_buttons_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_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+        </activity>
+
         <activity android:name=".audio.AudioLoopbackActivity"
                   android:label="@string/audio_loopback_test">
             <intent-filter>
@@ -2776,6 +2819,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
+             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
         </activity>
 
         <service android:name=".tv.MockTvInputService"
diff --git a/apps/CtsVerifier/jni/verifier/Android.mk b/apps/CtsVerifier/jni/verifier/Android.mk
index 2350085..1e43211 100644
--- a/apps/CtsVerifier/jni/verifier/Android.mk
+++ b/apps/CtsVerifier/jni/verifier/Android.mk
@@ -23,8 +23,7 @@
 
 LOCAL_SRC_FILES := \
 		CtsVerifierJniOnLoad.cpp \
-		com_android_cts_verifier_camera_StatsImage.cpp \
-		com_android_cts_verifier_os_FileUtils.cpp
+		com_android_cts_verifier_camera_StatsImage.cpp
 
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
diff --git a/apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp b/apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp
index 399275b..158fca4 100644
--- a/apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp
+++ b/apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp
@@ -17,7 +17,6 @@
 #include <jni.h>
 #include <stdio.h>
 
-extern int register_com_android_cts_verifier_os_FileUtils(JNIEnv*);
 extern int register_com_android_cts_verifier_camera_its_StatsImage(JNIEnv*);
 
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
@@ -27,10 +26,6 @@
         return JNI_ERR;
     }
 
-    if (register_com_android_cts_verifier_os_FileUtils(env)) {
-        return JNI_ERR;
-    }
-
     if (register_com_android_cts_verifier_camera_its_StatsImage(env)) {
         return JNI_ERR;
     }
diff --git a/apps/CtsVerifier/jni/verifier/com_android_cts_verifier_os_FileUtils.cpp b/apps/CtsVerifier/jni/verifier/com_android_cts_verifier_os_FileUtils.cpp
deleted file mode 100644
index 9e204e2..0000000
--- a/apps/CtsVerifier/jni/verifier/com_android_cts_verifier_os_FileUtils.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/* 
- * Copyright (C) 2010 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.
- */
-
-#include <jni.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <assert.h>
-#include <grp.h>
-#include <pwd.h>
-#include <unistd.h>
-
-static jfieldID gFileStatusDevFieldID;
-static jfieldID gFileStatusInoFieldID;
-static jfieldID gFileStatusModeFieldID;
-static jfieldID gFileStatusNlinkFieldID;
-static jfieldID gFileStatusUidFieldID;
-static jfieldID gFileStatusGidFieldID;
-static jfieldID gFileStatusSizeFieldID;
-static jfieldID gFileStatusBlksizeFieldID;
-static jfieldID gFileStatusBlocksFieldID;
-static jfieldID gFileStatusAtimeFieldID;
-static jfieldID gFileStatusMtimeFieldID;
-static jfieldID gFileStatusCtimeFieldID;
-static jfieldID gFileStatusExecutableID;
-
-/* Copied from hidden API: frameworks/base/core/jni/android_os_FileUtils.cpp */
-jboolean com_android_cts_verifier_os_FileUtils_getFileStatus(JNIEnv* env, jobject thiz,
-        jstring path, jobject fileStatus, jboolean statLinks)
-{
-    const char* pathStr = env->GetStringUTFChars(path, NULL);
-    jboolean ret = false;
-    struct stat s;
-
-    int res = statLinks == true ? lstat(pathStr, &s) : stat(pathStr, &s);
-
-    if (res == 0) {
-        ret = true;
-        if (fileStatus != NULL) {
-            env->SetIntField(fileStatus, gFileStatusDevFieldID, s.st_dev);
-            env->SetIntField(fileStatus, gFileStatusInoFieldID, s.st_ino);
-            env->SetIntField(fileStatus, gFileStatusModeFieldID, s.st_mode);
-            env->SetIntField(fileStatus, gFileStatusNlinkFieldID, s.st_nlink);
-            env->SetIntField(fileStatus, gFileStatusUidFieldID, s.st_uid);
-            env->SetIntField(fileStatus, gFileStatusGidFieldID, s.st_gid);
-            env->SetLongField(fileStatus, gFileStatusSizeFieldID, s.st_size);
-            env->SetIntField(fileStatus, gFileStatusBlksizeFieldID, s.st_blksize);
-            env->SetLongField(fileStatus, gFileStatusBlocksFieldID, s.st_blocks);
-            env->SetLongField(fileStatus, gFileStatusAtimeFieldID, s.st_atime);
-            env->SetLongField(fileStatus, gFileStatusMtimeFieldID, s.st_mtime);
-            env->SetLongField(fileStatus, gFileStatusCtimeFieldID, s.st_ctime);
-        }
-        if (access(pathStr, X_OK) == 0) {
-            env->SetBooleanField(fileStatus, gFileStatusExecutableID, JNI_TRUE);
-        } else {
-            env->SetBooleanField(fileStatus, gFileStatusExecutableID, JNI_FALSE);
-        }
-    }
-
-    env->ReleaseStringUTFChars(path, pathStr);
-
-    return ret;
-}
-
-jstring com_android_cts_verifier_os_FileUtils_getUserName(JNIEnv* env, jobject thiz,
-        jint uid)
-{
-    struct passwd *pwd = getpwuid(uid);
-    return env->NewStringUTF(pwd->pw_name);
-}
-
-jstring com_android_cts_verifier_os_FileUtils_getGroupName(JNIEnv* env, jobject thiz,
-        jint gid)
-{
-    struct group *grp = getgrgid(gid);
-    return env->NewStringUTF(grp->gr_name);
-}
-
-static JNINativeMethod gMethods[] = {
-    {  "getFileStatus", "(Ljava/lang/String;Lcom/android/cts/verifier/os/FileUtils$FileStatus;Z)Z",
-            (void *) com_android_cts_verifier_os_FileUtils_getFileStatus  },
-    {  "getUserName", "(I)Ljava/lang/String;",
-            (void *) com_android_cts_verifier_os_FileUtils_getUserName  },
-    {  "getGroupName", "(I)Ljava/lang/String;",
-            (void *) com_android_cts_verifier_os_FileUtils_getGroupName  },
-};
-
-int register_com_android_cts_verifier_os_FileUtils(JNIEnv* env)
-{
-    jclass clazz = env->FindClass("com/android/cts/verifier/os/FileUtils");
-    assert(clazz != null);
-
-    jclass fileStatusClass = env->FindClass("com/android/cts/verifier/os/FileUtils$FileStatus");
-    assert(fileStatusClass != null);
-    gFileStatusDevFieldID = env->GetFieldID(fileStatusClass, "dev", "I");
-    gFileStatusInoFieldID = env->GetFieldID(fileStatusClass, "ino", "I");
-    gFileStatusModeFieldID = env->GetFieldID(fileStatusClass, "mode", "I");
-    gFileStatusNlinkFieldID = env->GetFieldID(fileStatusClass, "nlink", "I");
-    gFileStatusUidFieldID = env->GetFieldID(fileStatusClass, "uid", "I");
-    gFileStatusGidFieldID = env->GetFieldID(fileStatusClass, "gid", "I");
-    gFileStatusSizeFieldID = env->GetFieldID(fileStatusClass, "size", "J");
-    gFileStatusBlksizeFieldID = env->GetFieldID(fileStatusClass, "blksize", "I");
-    gFileStatusBlocksFieldID = env->GetFieldID(fileStatusClass, "blocks", "J");
-    gFileStatusAtimeFieldID = env->GetFieldID(fileStatusClass, "atime", "J");
-    gFileStatusMtimeFieldID = env->GetFieldID(fileStatusClass, "mtime", "J");
-    gFileStatusCtimeFieldID = env->GetFieldID(fileStatusClass, "ctime", "J");
-    gFileStatusExecutableID = env->GetFieldID(fileStatusClass, "executable", "Z");
-
-    return env->RegisterNatives(clazz, gMethods, 
-            sizeof(gMethods) / sizeof(JNINativeMethod)); 
-}
diff --git a/apps/CtsVerifier/proguard.flags b/apps/CtsVerifier/proguard.flags
index 9f7ffcd..a28ee56 100644
--- a/apps/CtsVerifier/proguard.flags
+++ b/apps/CtsVerifier/proguard.flags
@@ -2,10 +2,6 @@
     native <methods>;
 }
 
--keepclassmembers class com.android.cts.verifier.os.FileUtils$FileStatus {
-    private <fields>;
-}
-
 # ensure we keep public sensor test methods, these are needed at runtime
 -keepclassmembers class * extends com.android.cts.verifier.sensors.base.BaseSensorTestActivity {
     public <methods>;
diff --git a/apps/CtsVerifier/res/layout/uap_attribs_panel.xml b/apps/CtsVerifier/res/layout/uap_attribs_panel.xml
new file mode 100644
index 0000000..ea4e719
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_attribs_panel.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/scrollView"
+    style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/uap_profile_header"/>
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        >
+
+        <TextView
+            android:text="@string/uapPeripheralProfileStatus"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <TextView
+            android:text="status"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/uap_attribsStatusTx"
+            android:paddingLeft="16dp"/>
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/uap_buttons_panel.xml b/apps/CtsVerifier/res/layout/uap_buttons_panel.xml
new file mode 100644
index 0000000..2aeaca2
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_buttons_panel.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/scrollView"
+    style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <include layout="@layout/uap_profile_header"/>
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TextView
+            android:text="@string/uapButtonTestInstructions"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+        <TextView
+            android:text="@string/uapButtonsBtnALbl"
+            android:id="@+id/uap_buttonsBtnALabelTx"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <TextView
+            android:text="@string/uapButtonsNotRecognized"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/uap_buttonsBtnAStatusTx"
+            android:paddingLeft="16dp"/>
+
+        <TextView
+            android:text="@string/uapButtonsBtnBLbl"
+            android:id="@+id/uap_buttonsBtnBLabelTx"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <TextView
+            android:text="@string/uapButtonsNotRecognized"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/uap_buttonsBtnBStatusTx"
+            android:paddingLeft="16dp"/>
+
+        <TextView
+            android:text="@string/uapButtonsBtnCLbl"
+            android:id="@+id/uap_buttonsBtnCLabelTx"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <TextView
+            android:text="@string/uapButtonsNotRecognized"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/uap_buttonsBtnCStatusTx"
+            android:paddingLeft="16dp"/>
+
+        <TextView
+            android:text="@string/uapButtonsBtnDLbl"
+            android:id="@+id/uap_buttonsBtnDLabelTx"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <TextView
+            android:text="@string/uapButtonsNotRecognized"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/uap_buttonsBtnDStatusTx"
+            android:paddingLeft="16dp"/>
+        </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/uap_play_panel.xml b/apps/CtsVerifier/res/layout/uap_play_panel.xml
new file mode 100644
index 0000000..1cb469a
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_play_panel.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/scrollView"
+    style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <include layout="@layout/uap_profile_header"/>
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TextView
+            android:text="@string/uapPlayTestInstructions"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+        <Button
+            android:text="@string/audio_uap_play_playBtn"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/uap_playPlayBtn"
+            android:nextFocusUp="@+id/fail_button"
+            android:nextFocusDown="@+id/pass_button"/>
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/uap_profile_header.xml b/apps/CtsVerifier/res/layout/uap_profile_header.xml
new file mode 100644
index 0000000..ca3af8f
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_profile_header.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content">
+    <TextView
+        android:text="@string/profileLabel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/uap_profileNameTx"
+        android:paddingLeft="16dp"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/uap_profileDescriptionTx"
+        android:paddingLeft="16dp"/>
+
+    <TextView
+        android:text="@string/connectedPeripheral"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/uap_peripheralNameTx"
+        android:paddingLeft="16dp"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/uap_record_panel.xml b/apps/CtsVerifier/res/layout/uap_record_panel.xml
new file mode 100644
index 0000000..5277389
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_record_panel.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/scrollView"
+    style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <include layout="@layout/uap_profile_header"/>
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:text="@string/uapRecordTestInstructions"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+        <Button
+            android:text="@string/audio_uap_record_recordBtn"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/uap_recordRecordBtn"
+            android:nextFocusUp="@+id/fail_button"
+            android:nextFocusDown="@+id/uap_recordRecordLoopBtn"/>
+
+        <Button
+            android:text="@string/audio_uap_record_recordLoopbackBtn"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/uap_recordRecordLoopBtn"
+            android:nextFocusUp="@+id/uap_recordRecordBtn"
+            android:nextFocusDown="@+id/pass_button"/>
+        </LinearLayout>
+
+    <com.android.cts.verifier.audio.audiolib.WaveScopeView
+        android:id="@+id/uap_recordWaveView"
+        android:layout_width="match_parent"
+        android:layout_height="256dp"/>
+
+    <include layout="@layout/pass_fail_buttons"/>
+
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
old mode 100644
new mode 100755
index b522c61..ffbf945
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -414,8 +414,8 @@
     <string name="ble_low">Low</string>
     <string name="ble_medium">Medium</string>
     <string name="ble_high">High</string>
-    <string name="ble_scanner_power_level_instruction">Count: Ultra low &lt; low &lt; medium &lt; high\nRssi: Ultra low &lt; low &lt; medium &lt; high\nDistance to see count freezing: Ultra low &lt; low &lt; medium &lt; high\nA common error is ultra low, low and medium behave similarly, with similar rssi, freeze at similar distance.\n\n All power level receive a different mac address. After 15 mins, a green text "Get a new Mac address" will show up.</string>
-    <string name="ble_scanner_scan_filter_name">Bluetooth LE Hardware Scan Filter</string>
+    <string name="ble_scanner_power_level_instruction">Count: Ultra low &lt; low &lt; medium &lt; high\nRssi: Ultra low &lt; low &lt; medium &lt; high\nDistance to see count freezing: Ultra low &lt; low &lt; medium &lt; high\nA common error is ultra low, low and medium behave similarly, with similar rssi, freeze at similar distance.\n\n All power level receive a mac address.</string>
+    <string name="ble_scanner_scan_filter_name">BLE Hardware Scan Filter</string>
     <string name="ble_scanner_scan_filter_info">Lock the screen of scanner, and connect to monsoon. It will not wake up when advertiser is advertising unscannable, and scanner is scanning with filter.</string>
     <string name="ble_scanner_scan_filter_instruction">Scan filter is to scan data with service UUID = 0x6666 only. If you scan without scan filter, data with service UUID = 0x5555 and 0x6666 will show up on screen.\nFor monsoon test:\n\tClick scan with filter, lock the screen, connect to monsoon. It will not wake up when advertiser is advertising unscannable data packets, but will show a peak in power usage when advertiser is advertising scannable data.\nFor logcat test:\n\tClick scan with filter, logcat the scanner. No data will be received by GattService when advertiser is advertising unscannable data.</string>
     <string name="ble_scan_with_filter">Scan with filter</string>
@@ -3255,6 +3255,36 @@
     <string name="audio_routingnotification_trackRoutingMsg">AudioTrack rerouting</string>
     <string name="audio_routingnotification_recordRoutingMsg">AudioRecord rerouting</string>
 
+    <!-- USB Audio Peripheral Tests -->
+    <string name="profileLabel">Profile</string>
+    <string name="connectedPeripheral">Connected Peripheral</string>
+    <string name="audio_uap_attribs_test">USB Audio Peripheral Attributes Test</string>
+    <string name="uapPeripheralProfileStatus">Peripheral Profile Status</string>
+
+    <string name="audio_uap_play_test">USB Audio Peripheral Play Test</string>
+    <string name="uapPlayTestInstructions">Connect the USB Audio Peripheral and press the PLAY button below.
+        Verify that a tone is correctly played.</string>
+    <string name="audio_uap_play_playBtn">Play</string>
+    <string name="audio_uap_play_stopBtn">Stop</string>
+
+    <string name="audio_uap_record_test">USB Audio Peripheral Record Test</string>
+    <string name="uapRecordTestInstructions">Connect the USB Audio Peripheral and press the RECORD or
+        RECORD LOOPBACK button below. Verify that a tone is correctly played.</string>
+    <string name="audio_uap_record_recordBtn">Record</string>
+    <string name="audio_uap_record_recordLoopbackBtn">Record Loopback</string>
+    <string name="audio_uap_record_stopBtn">Stop</string>
+
+    <string name="audio_uap_buttons_test">USB Audio Peripheral Buttons Test</string>
+    <string name="uapButtonTestInstructions">Connect the USB Audio headset with buttons
+        and press each transport/media button in turn.</string>
+
+    <string name="uapButtonsBtnALbl">Button A - play/pause</string>
+    <string name="uapButtonsBtnBLbl">Button B - volume up (+)</string>
+    <string name="uapButtonsBtnCLbl">Button C - volume down (-)</string>
+    <string name="uapButtonsBtnDLbl">Button D - voice assist</string>
+    <string name="uapButtonsRecognized">Recognized</string>
+    <string name="uapButtonsNotRecognized">Not Recognized</string>
+
     <!-- Audio general text -->
     <string name="audio_general_headset_port_exists">Does this device have a headset port?</string>
     <string name="audio_general_headset_no">No</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
index 6a9c32f..1629e1b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
@@ -52,17 +52,17 @@
  */
 class ReportExporter extends AsyncTask<Void, Void, String> {
 
-    private static final String COMMAND_LINE_ARGS = "CtsVerifier";
+    private static final String COMMAND_LINE_ARGS = "";
     private static final String LOG_URL = null;
     private static final String REFERENCE_URL = null;
-    private static final String SUITE_NAME = "CTS_VERIFIER";
-    private static final String SUITE_PLAN = "CTSVERIFIER";
+    private static final String SUITE_NAME_METADATA_KEY = "SuiteName";
+    private static final String SUITE_PLAN = "verifier";
     private static final String SUITE_BUILD = "0";
 
     private static final long START_MS = System.currentTimeMillis();
     private static final long END_MS = START_MS;
 
-    private static final String REPORT_DIRECTORY = "ctsVerifierReports";
+    private static final String REPORT_DIRECTORY = "verifierReports";
     private static final String ZIP_EXTENSION = ".zip";
 
     protected static final Logger LOG = Logger.getLogger(ReportExporter.class.getName());
@@ -93,17 +93,20 @@
         File externalStorageDirectory = Environment.getExternalStorageDirectory();
         File verifierReportsDir = new File(externalStorageDirectory, REPORT_DIRECTORY);
         verifierReportsDir.mkdirs();
+
+        String suiteName = Version.getMetadata(mContext, SUITE_NAME_METADATA_KEY);
         // create a temporary directory for this particular report
-        File tempDir = new File(verifierReportsDir, getReportName());
+        File tempDir = new File(verifierReportsDir, getReportName(suiteName));
         tempDir.mkdirs();
 
         // create a File object for a report ZIP file
-        File reportZipFile = new File(verifierReportsDir, getReportName() + ZIP_EXTENSION);
+        File reportZipFile = new File(
+                verifierReportsDir, getReportName(suiteName) + ZIP_EXTENSION);
 
         try {
             // Serialize the report
             String versionName = Version.getVersionName(mContext);
-            ResultHandler.writeResults(SUITE_NAME, versionName, SUITE_PLAN, SUITE_BUILD,
+            ResultHandler.writeResults(suiteName, versionName, SUITE_PLAN, SUITE_BUILD,
                     result, tempDir, START_MS, END_MS, REFERENCE_URL, LOG_URL,
                     COMMAND_LINE_ARGS);
 
@@ -147,11 +150,11 @@
         }
     }
 
-    private String getReportName() {
+    private String getReportName(String suiteName) {
         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss", Locale.ENGLISH);
         String date = dateFormat.format(new Date());
-        return String.format( "%s-%s-%s-%s-%s",
-                date, Build.MANUFACTURER, Build.PRODUCT, Build.DEVICE, Build.ID);
+        return String.format( "%s-%s-%s-%s-%s-%s",
+                date, suiteName, Build.MANUFACTURER, Build.PRODUCT, Build.DEVICE, Build.ID);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/Version.java b/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
index e7b6121..272fbcd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
@@ -17,12 +17,18 @@
 package com.android.cts.verifier;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
 
 class Version {
 
+    private static final String TAG = Version.class.getSimpleName();
+
+    private static final String UNKNOWN = "unknown";
+
     static String getVersionName(Context context) {
         return getPackageInfo(context).versionName;
     }
@@ -40,4 +46,19 @@
                     + context.getPackageName());
         }
     }
+
+    static String getMetadata(Context context, String name) {
+        try {
+            PackageManager packageManager = context.getPackageManager();
+            ApplicationInfo applicationInfo = packageManager.getApplicationInfo(
+                    context.getPackageName(), PackageManager.GET_META_DATA);
+            String value = applicationInfo.metaData.getString(name);
+            if (value != null) {
+                return value;
+            }
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Version.getMetadata: " + name, e);
+        }
+        return UNKNOWN;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
new file mode 100644
index 0000000..21a70b9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
@@ -0,0 +1,167 @@
+/*
+ * 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.audio;
+
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+import com.android.cts.verifier.audio.peripheralprofile.ProfileManager;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+public abstract class USBAudioPeripheralActivity extends PassFailButtons.Activity {
+    private static final String TAG = "USBAudioPeripheralActivity";
+
+    // Profile
+    protected ProfileManager mProfileManager = new ProfileManager();
+    protected PeripheralProfile mSelectedProfile;
+
+    // Peripheral
+    AudioManager mAudioManager;
+    protected boolean mIsPeripheralAttached;
+    protected AudioDeviceInfo mOutputDevInfo;
+    protected AudioDeviceInfo mInputDevInfo;
+
+    // This will be overriden...
+    protected  int mSystemSampleRate = 48000;
+
+    // Widgets
+    private TextView mProfileNameTx;
+    private TextView mProfileDescriptionTx;
+
+    private TextView mPeripheralNameTx;
+
+    public USBAudioPeripheralActivity() {
+        super();
+
+        mProfileManager.loadProfiles();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+        mSystemSampleRate = Integer.parseInt(
+            mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
+    }
+
+    protected void connectPeripheralStatusWidgets() {
+        mProfileNameTx = (TextView)findViewById(R.id.uap_profileNameTx);
+        mProfileDescriptionTx =
+            (TextView)findViewById(R.id.uap_profileDescriptionTx);
+        mPeripheralNameTx = (TextView)findViewById(R.id.uap_peripheralNameTx);
+    }
+
+    private void showProfileStatus() {
+        if (mSelectedProfile != null) {
+            mProfileNameTx.setText(mSelectedProfile.getName());
+            mProfileDescriptionTx.setText(mSelectedProfile.getDescription());
+        } else {
+            mProfileNameTx.setText("");
+            mProfileDescriptionTx.setText("");
+        }
+    }
+
+    private void showPeripheralStatus() {
+        if (mIsPeripheralAttached) {
+            if (mOutputDevInfo != null) {
+                mPeripheralNameTx.setText(mOutputDevInfo.getProductName().toString());
+            } else if (mInputDevInfo != null) {
+                mPeripheralNameTx.setText(mInputDevInfo.getProductName().toString());
+            }
+        } else {
+            mPeripheralNameTx.setText("Disconnected");
+        }
+    }
+
+    private void scanPeripheralList(AudioDeviceInfo[] devices) {
+        // Can't just use the first record because then we will only get
+        // Source OR sink, not both even on devices that are both.
+        mOutputDevInfo = null;
+        mInputDevInfo = null;
+
+        // Any valid peripherals
+        for(AudioDeviceInfo devInfo : devices) {
+            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE) {
+                if (devInfo.isSink()) {
+                    mOutputDevInfo = devInfo;
+                }
+                if (devInfo.isSource()) {
+                    mInputDevInfo = devInfo;
+                }
+            }
+        }
+        mIsPeripheralAttached = mOutputDevInfo != null || mInputDevInfo != null;
+        // Log.i(TAG, "mIsPeripheralAttached: " + mIsPeripheralAttached);
+
+        // any associated profiles?
+        if (mIsPeripheralAttached) {
+            if (mOutputDevInfo != null) {
+                mSelectedProfile =
+                    mProfileManager.getProfile(mOutputDevInfo.getProductName().toString());
+            } else if (mInputDevInfo != null) {
+                mSelectedProfile =
+                    mProfileManager.getProfile(mInputDevInfo.getProductName().toString());
+            }
+        } else {
+            mSelectedProfile = null;
+        }
+
+    }
+
+    private class ConnectListener extends AudioDeviceCallback {
+        /*package*/ ConnectListener() {}
+
+        //
+        // AudioDevicesManager.OnDeviceConnectionListener
+        //
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            // Log.i(TAG, "onAudioDevicesAdded() num:" + addedDevices.length);
+
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+
+            showProfileStatus();
+            showPeripheralStatus();
+            updateConnectStatus();
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            // Log.i(TAG, "onAudioDevicesRemoved() num:" + removedDevices.length);
+
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+
+            showProfileStatus();
+            showPeripheralStatus();
+            updateConnectStatus();
+        }
+    }
+
+    abstract public void updateConnectStatus();
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
new file mode 100644
index 0000000..f0c75c4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
@@ -0,0 +1,131 @@
+/*
+ * 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.audio;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.cts.verifier.audio.peripheralprofile.ListsHelper;
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+public class USBAudioPeripheralAttributesActivity extends USBAudioPeripheralActivity {
+    private static final String TAG = "USBAudioPeripheralAttributesActivity";
+
+    private TextView mTestStatusTx;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.uap_attribs_panel);
+
+        connectPeripheralStatusWidgets();
+
+        mTestStatusTx = (TextView)findViewById(R.id.uap_attribsStatusTx);
+
+        setPassFailButtonClickListeners();
+    }
+
+    //
+    // USBAudioPeripheralActivity
+    //
+    public void updateConnectStatus() {
+        boolean outPass = false;
+        boolean inPass = false;
+        if (mIsPeripheralAttached && mSelectedProfile != null) {
+            boolean match = true;
+            StringBuilder metaSb = new StringBuilder();
+
+            // Outputs
+            if (mOutputDevInfo != null) {
+                AudioDeviceInfo deviceInfo = mOutputDevInfo;
+                PeripheralProfile.ProfileAttributes attribs =
+                    mSelectedProfile.getOutputAttributes();
+                StringBuilder sb = new StringBuilder();
+
+                if (!ListsHelper.isMatch(deviceInfo.getChannelCounts(), attribs.mChannelCounts)) {
+                    sb.append("Output - Channel Counts Mismatch\n");
+                }
+                if (!ListsHelper.isMatch(deviceInfo.getChannelIndexMasks(),
+                                         attribs.mChannelIndexMasks)) {
+                    sb.append("Output - Channel Index Masks Mismatch\n");
+                }
+                if (!ListsHelper.isMatch(deviceInfo.getChannelMasks(),
+                                         attribs.mChannelPositionMasks)) {
+                    sb.append("Output - Channel Position Masks Mismatch\n");
+                }
+                if (!ListsHelper.isMatch(deviceInfo.getEncodings(), attribs.mEncodings)) {
+                    sb.append("Output - Encodings Mismatch\n");
+                }
+                if (!ListsHelper.isMatch(deviceInfo.getSampleRates(), attribs.mSampleRates)) {
+                    sb.append("Output - Sample Rates Mismatch\n");
+                }
+
+                if (sb.toString().length() == 0){
+                    metaSb.append("Output - Match\n");
+                    outPass = true;
+                } else {
+                    metaSb.append(sb.toString());
+                }
+            }
+
+            // Inputs
+            if (mInputDevInfo != null) {
+                AudioDeviceInfo deviceInfo = mInputDevInfo;
+                PeripheralProfile.ProfileAttributes attribs =
+                    mSelectedProfile.getInputAttributes();
+                StringBuilder sb = new StringBuilder();
+
+                if (!ListsHelper.isMatch(deviceInfo.getChannelCounts(), attribs.mChannelCounts)) {
+                    sb.append("Input - Channel Counts Mismatch\n");
+                }
+                if (!ListsHelper.isMatch(deviceInfo.getChannelIndexMasks(),
+                                         attribs.mChannelIndexMasks)) {
+                    sb.append("Input - Channel Index Masks Mismatch\n");
+                }
+                if (!ListsHelper.isMatch(deviceInfo.getChannelMasks(),
+                                         attribs.mChannelPositionMasks)) {
+                    sb.append("Input - Channel Position Masks Mismatch\n");
+                }
+                if (!ListsHelper.isMatch(deviceInfo.getEncodings(), attribs.mEncodings)) {
+                    sb.append("Input - Encodings Mismatch\n");
+                }
+                if (!ListsHelper.isMatch(deviceInfo.getSampleRates(), attribs.mSampleRates)) {
+                    sb.append("Input - Sample Rates Mismatch\n");
+                }
+
+                if (sb.toString().length() == 0){
+                    inPass = true;
+                    metaSb.append("Input - Match\n");
+                } else {
+                    metaSb.append(sb.toString());
+                }
+            }
+
+            mTestStatusTx.setText(metaSb.toString());
+        } else {
+            mTestStatusTx.setText("No Peripheral or No Matching Profile.");
+        }
+
+        //TODO we need to support output-only and input-only peripherals
+        getPassButton().setEnabled(outPass && inPass);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java
new file mode 100644
index 0000000..fbaf877
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java
@@ -0,0 +1,187 @@
+/*
+ * 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.audio;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.widget.TextView;
+
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+import com.android.cts.verifier.audio.peripheralprofile.ProfileButtonAttributes;
+
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+public class USBAudioPeripheralButtonsActivity extends USBAudioPeripheralActivity {
+    private static final String TAG = "USBAudioPeripheralButtonsActivity";
+
+    // State
+    private boolean mHasBtnA;
+    private boolean mHasBtnB;
+    private boolean mHasBtnC;
+    private boolean mHasBtnD;
+
+    // Widgets
+    private TextView mBtnALabelTxt;
+    private TextView mBtnBLabelTxt;
+    private TextView mBtnCLabelTxt;
+    private TextView mBtnDLabelTxt;
+
+    private TextView mBtnAStatusTxt;
+    private TextView mBtnBStatusTxt;
+    private TextView mBtnCStatusTxt;
+    private TextView mBtnDStatusTxt;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.uap_buttons_panel);
+
+        connectPeripheralStatusWidgets();
+
+        mBtnALabelTxt = (TextView)findViewById(R.id.uap_buttonsBtnALabelTx);
+        mBtnBLabelTxt = (TextView)findViewById(R.id.uap_buttonsBtnBLabelTx);
+        mBtnCLabelTxt = (TextView)findViewById(R.id.uap_buttonsBtnCLabelTx);
+        mBtnDLabelTxt = (TextView)findViewById(R.id.uap_buttonsBtnDLabelTx);
+
+        mBtnAStatusTxt = (TextView)findViewById(R.id.uap_buttonsBtnAStatusTx);
+        mBtnBStatusTxt = (TextView)findViewById(R.id.uap_buttonsBtnBStatusTx);
+        mBtnCStatusTxt = (TextView)findViewById(R.id.uap_buttonsBtnCStatusTx);
+        mBtnDStatusTxt = (TextView)findViewById(R.id.uap_buttonsBtnDStatusTx);
+
+        setPassFailButtonClickListeners();
+    }
+
+    private void showButtonsState() {
+        if (mIsPeripheralAttached && mSelectedProfile != null) {
+            ProfileButtonAttributes mButtonAttributes = mSelectedProfile.getButtonAttributes();
+            if (mButtonAttributes != null) {
+                if (!mButtonAttributes.mHasBtnA) {
+                    mBtnALabelTxt.setTextColor(Color.GRAY);
+                    mBtnAStatusTxt.setTextColor(Color.GRAY);
+                } else {
+                    mBtnALabelTxt.setTextColor(Color.WHITE);
+                    mBtnAStatusTxt.setTextColor(Color.WHITE);
+                }
+                if (!mButtonAttributes.mHasBtnB) {
+                    mBtnBLabelTxt.setTextColor(Color.GRAY);
+                    mBtnBStatusTxt.setTextColor(Color.GRAY);
+                } else {
+                    mBtnBLabelTxt.setTextColor(Color.WHITE);
+                    mBtnBStatusTxt.setTextColor(Color.WHITE);
+                }
+                if (!mButtonAttributes.mHasBtnC) {
+                    mBtnCLabelTxt.setTextColor(Color.GRAY);
+                    mBtnCStatusTxt.setTextColor(Color.GRAY);
+                } else {
+                    mBtnCLabelTxt.setTextColor(Color.WHITE);
+                    mBtnCStatusTxt.setTextColor(Color.WHITE);
+                }
+                if (!mButtonAttributes.mHasBtnD) {
+                    mBtnDLabelTxt.setTextColor(Color.GRAY);
+                    mBtnDStatusTxt.setTextColor(Color.GRAY);
+                } else {
+                    mBtnDLabelTxt.setTextColor(Color.WHITE);
+                    mBtnDStatusTxt.setTextColor(Color.WHITE);
+                }
+            } else {
+                mBtnALabelTxt.setTextColor(Color.GRAY);
+                mBtnAStatusTxt.setTextColor(Color.GRAY);
+                mBtnBLabelTxt.setTextColor(Color.GRAY);
+                mBtnBStatusTxt.setTextColor(Color.GRAY);
+                mBtnCLabelTxt.setTextColor(Color.GRAY);
+                mBtnCStatusTxt.setTextColor(Color.GRAY);
+                mBtnDLabelTxt.setTextColor(Color.GRAY);
+                mBtnDStatusTxt.setTextColor(Color.GRAY);
+            }
+        }
+
+        mBtnAStatusTxt.setText(getString(
+            mHasBtnA ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
+        mBtnBStatusTxt.setText(getString(
+            mHasBtnB ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
+        mBtnCStatusTxt.setText(getString(
+            mHasBtnC ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
+        mBtnDStatusTxt.setText(getString(
+            mHasBtnD ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
+    }
+
+    private void calculateMatch() {
+        if (mIsPeripheralAttached && mSelectedProfile != null) {
+            ProfileButtonAttributes mButtonAttributes = mSelectedProfile.getButtonAttributes();
+            boolean match = mButtonAttributes != null;
+            if (match && mButtonAttributes.mHasBtnA != mHasBtnA) {
+                match = false;
+            }
+            if (match && mButtonAttributes.mHasBtnB != mHasBtnB) {
+                match = false;
+            }
+            if (match && mButtonAttributes.mHasBtnC != mHasBtnC) {
+                match = false;
+            }
+            if (match && mButtonAttributes.mHasBtnD != mHasBtnD) {
+                match = false;
+            }
+            Log.i(TAG, "match:" + match);
+            getPassButton().setEnabled(match);
+        } else {
+            getPassButton().setEnabled(false);
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        Log.i(TAG, "onKeyDown(" + keyCode + ")");
+        switch (keyCode) {
+        // Function A control event
+        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+            mHasBtnA = true;
+            break;
+
+        // Function B control event
+        case KeyEvent.KEYCODE_VOLUME_UP:
+            mHasBtnB = true;
+            break;
+
+        // Function C control event
+        case KeyEvent.KEYCODE_VOLUME_DOWN:
+            mHasBtnC = true;
+            break;
+
+        // Function D control event
+        case KeyEvent.KEYCODE_VOICE_ASSIST:
+            mHasBtnD = true;
+            break;
+        }
+
+        showButtonsState();
+        calculateMatch();
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    //
+    // USBAudioPeripheralActivity
+    //
+    public void updateConnectStatus() {
+        mHasBtnA = mHasBtnB = mHasBtnC = mHasBtnD = false;
+        showButtonsState();
+        calculateMatch();
+    }
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
new file mode 100644
index 0000000..c2b3b2c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
@@ -0,0 +1,74 @@
+/*
+ * 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.audio;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+public class USBAudioPeripheralPlayActivity extends USBAudioPeripheralPlayerActivity {
+    private static final String TAG = "USBAudioPeripheralPlayActivity";
+
+    // Widgets
+    private Button mPlayBtn;
+    private LocalClickListener mButtonClickListener = new LocalClickListener();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.uap_play_panel);
+
+        connectPeripheralStatusWidgets();
+
+        // Local widgets
+        mPlayBtn = (Button)findViewById(R.id.uap_playPlayBtn);
+        mPlayBtn.setOnClickListener(mButtonClickListener);
+
+        setupPlayer();
+
+        setPassFailButtonClickListeners();
+    }
+
+    //
+    // USBAudioPeripheralActivity
+    //
+    public void updateConnectStatus() {
+        getPassButton().setEnabled(mOutputDevInfo != null);
+    }
+
+    public class LocalClickListener implements View.OnClickListener {
+        @Override
+        public void onClick(View view) {
+            switch (view.getId()) {
+            case R.id.uap_playPlayBtn:
+                Log.i(TAG, "Play Button Pressed");
+                if (!isPlaying()) {
+                    startPlay();
+                    mPlayBtn.setText(getString(R.string.audio_uap_play_stopBtn));
+                } else {
+                    stopPlay();
+                    mPlayBtn.setText(getString(R.string.audio_uap_play_playBtn));
+                }
+                break;
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
new file mode 100644
index 0000000..675d658
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
@@ -0,0 +1,77 @@
+/*
+ * 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.audio;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.util.Log;
+
+import com.android.cts.verifier.audio.audiolib.SignalGenerator;
+import com.android.cts.verifier.audio.audiolib.StreamPlayer;
+import com.android.cts.verifier.audio.audiolib.WaveTableFloatFiller;
+import com.android.cts.verifier.audio.peripheralprofile.USBDeviceInfoHelper;
+
+public abstract class USBAudioPeripheralPlayerActivity extends USBAudioPeripheralActivity {
+    private static final String TAG = "USBAudioPeripheralPlayerActivity";
+
+    protected  int mSystemBufferSize;
+
+    // Player
+    protected boolean mIsPlaying = false;
+    protected StreamPlayer mPlayer = null;
+    protected WaveTableFloatFiller mFiller = null;
+
+    protected float[] mWavBuffer = null;
+
+    protected boolean mOverridePlayFlag = true;
+
+    private static final int WAVBUFF_SIZE_IN_SAMPLES = 2048;
+
+    protected void setupPlayer() {
+        mSystemBufferSize =
+            StreamPlayer.calcNumBurstFrames((AudioManager)getSystemService(Context.AUDIO_SERVICE));
+
+        // the +1 is so we can repeat the 0th sample and simplify the interpolation calculation.
+        mWavBuffer = new float[WAVBUFF_SIZE_IN_SAMPLES + 1];
+
+        SignalGenerator.fillFloatSine(mWavBuffer);
+        mFiller = new WaveTableFloatFiller(mWavBuffer);
+
+        mPlayer = new StreamPlayer();
+    }
+
+    protected void startPlay() {
+        if (mOutputDevInfo != null && !mIsPlaying) {
+            int numChans = USBDeviceInfoHelper.calcMaxChannelCount(mOutputDevInfo);
+            mPlayer.open(numChans, mSystemSampleRate, mSystemBufferSize, mFiller);
+            mPlayer.start();
+            mIsPlaying = true;
+        }
+    }
+
+    protected void stopPlay() {
+        if (mIsPlaying) {
+            mPlayer.stop();
+            mPlayer.close();
+            mIsPlaying = false;
+        }
+    }
+
+    public boolean isPlaying() {
+        return mIsPlaying;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
new file mode 100644
index 0000000..41350c9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
@@ -0,0 +1,197 @@
+/*
+ * 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.audio;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.cts.verifier.audio.audiolib.StreamRecorder;
+import com.android.cts.verifier.audio.audiolib.StreamRecorderListener;
+import com.android.cts.verifier.audio.audiolib.WaveScopeView;
+
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+import com.android.cts.verifier.audio.peripheralprofile.USBDeviceInfoHelper;
+
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralPlayerActivity {
+    private static final String TAG = "USBAudioPeripheralRecordActivity";
+
+    // Recorder
+    private StreamRecorder mRecorder = null;
+    private RecordListener mRecordListener = null;
+    private boolean mIsRecording = false;
+
+    // Widgets
+    private Button mRecordBtn;
+    private Button mRecordLoopbackBtn;
+
+    private LocalClickListener mButtonClickListener = new LocalClickListener();
+
+    private WaveScopeView mWaveView = null;
+
+    private void connectWaveView() {
+        // Log.i(TAG, "connectWaveView() rec:" + (mRecorder != null));
+        if (mRecorder != null) {
+            float[] smplFloatBuff = mRecorder.getBurstBuffer();
+            int numChans = mRecorder.getNumChannels();
+            int numFrames = smplFloatBuff.length / numChans;
+            mWaveView.setPCMFloatBuff(smplFloatBuff, numChans, numFrames);
+            mWaveView.invalidate();
+
+            mRecorder.setListener(mRecordListener);
+        }
+    }
+
+    public boolean startRecording(boolean withLoopback) {
+        if (mInputDevInfo == null) {
+            return false;
+        }
+
+        if (mRecorder == null) {
+            mRecorder = new StreamRecorder();
+        } else if (mRecorder.isRecording()) {
+            mRecorder.stop();
+        }
+
+        int numChans = USBDeviceInfoHelper.calcMaxChannelCount(mInputDevInfo);
+
+        if (mRecorder.open(numChans, mSystemSampleRate, mSystemBufferSize)) {
+            connectWaveView();  // Setup the WaveView
+
+            mIsRecording = mRecorder.start();
+
+            if (withLoopback) {
+                startPlay();
+            }
+
+            return mIsRecording;
+        } else {
+            return false;
+        }
+    }
+
+    public void stopRecording() {
+        if (mRecorder != null) {
+            mRecorder.stop();
+        }
+
+        if (mPlayer != null && mPlayer.isPlaying()) {
+            mPlayer.stop();
+        }
+
+        mIsRecording = false;
+    }
+
+    public boolean isRecording() {
+        return mIsRecording;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.uap_record_panel);
+
+        connectPeripheralStatusWidgets();
+
+        // Local widgets
+        mRecordBtn = (Button)findViewById(R.id.uap_recordRecordBtn);
+        mRecordBtn.setOnClickListener(mButtonClickListener);
+        mRecordLoopbackBtn = (Button)findViewById(R.id.uap_recordRecordLoopBtn);
+        mRecordLoopbackBtn.setOnClickListener(mButtonClickListener);
+
+        setupPlayer();
+
+        mRecorder = new StreamRecorder();
+        mRecordListener = new RecordListener();
+
+        mWaveView = (WaveScopeView)findViewById(R.id.uap_recordWaveView);
+        mWaveView.setBackgroundColor(Color.DKGRAY);
+        mWaveView.setTraceColor(Color.WHITE);
+
+        setPassFailButtonClickListeners();
+    }
+
+    //
+    // USBAudioPeripheralActivity
+    //
+    public void updateConnectStatus() {
+        getPassButton().setEnabled(mOutputDevInfo != null);
+    }
+
+    public class LocalClickListener implements View.OnClickListener {
+        @Override
+        public void onClick(View view) {
+            switch (view.getId()) {
+            case R.id.uap_recordRecordBtn:
+                Log.i(TAG, "Record Button Pressed");
+                if (!isPlaying()) {
+                    startRecording(false);
+                    mRecordBtn.setText(getString(R.string.audio_uap_record_stopBtn));
+                    mRecordLoopbackBtn.setEnabled(false);
+                } else {
+                    stopRecording();
+                    mRecordBtn.setText(getString(R.string.audio_uap_record_recordBtn));
+                    mRecordLoopbackBtn.setEnabled(true);
+                }
+                break;
+
+            case R.id.uap_recordRecordLoopBtn:
+                Log.i(TAG, "Record Loopback Button Pressed");
+                if (!isPlaying()) {
+                    startRecording(true);
+                    mRecordLoopbackBtn.setText(getString(R.string.audio_uap_record_stopBtn));
+                    mRecordBtn.setEnabled(false);
+                } else {
+                    stopRecording();
+                    mRecordLoopbackBtn.setText(
+                        getString(R.string.audio_uap_record_recordLoopbackBtn));
+                    mRecordBtn.setEnabled(true);
+                }
+                break;
+            }
+        }
+    }
+
+    private class RecordListener extends StreamRecorderListener {
+        /*package*/ RecordListener() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            // Log.i(TAG, "RecordListener.HandleMessage(" + msg.what + ")");
+            switch (msg.what) {
+                case MSG_START:
+                    break;
+
+                case MSG_BUFFER_FILL:
+                    mWaveView.invalidate();
+                    break;
+
+                case MSG_STOP:
+                    break;
+            }
+        }
+    }
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java
new file mode 100644
index 0000000..fd4d6c9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java
@@ -0,0 +1,37 @@
+/*
+ * 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.audio.audiolib;
+
+/**
+ * An interface for objects which provide streamed audio data to a StreamPlayer instance.
+ */
+public interface AudioFiller {
+    /**
+     * Reset a stream to the beginning.
+     */
+    public void reset();
+
+    /**
+     * Process a request for audio data.
+     * @param buffer The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * @return The number of frames actually generated. If this value is less than that
+     * requested, it may be interpreted by the player as the end of playback.
+     */
+    public int fill(float[] buffer, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java
new file mode 100644
index 0000000..002f460
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.audio.audiolib;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+
+// TODO - This functionality probably exists in the framework function. Remove this and
+//    use that instead.
+public class AudioUtils {
+    @SuppressWarnings("unused")
+    private static final String TAG = "AudioUtils";
+
+    public static int countIndexChannels(int chanConfig) {
+        return Integer.bitCount(chanConfig & ~0x80000000);
+    }
+
+    public static int countToIndexMask(int chanCount) {
+        return  (1 << chanCount) - 1;
+    }
+
+    public static int countToOutPositionMask(int channelCount) {
+        switch (channelCount) {
+            case 1:
+                return AudioFormat.CHANNEL_OUT_MONO;
+
+            case 2:
+                return AudioFormat.CHANNEL_OUT_STEREO;
+
+            case 4:
+                return AudioFormat.CHANNEL_OUT_QUAD;
+
+            default:
+                return AudioTrack.ERROR_BAD_VALUE;
+        }
+    }
+
+    public static int countToInPositionMask(int channelCount) {
+        switch (channelCount) {
+            case 1:
+                return AudioFormat.CHANNEL_IN_MONO;
+
+            case 2:
+                return AudioFormat.CHANNEL_IN_STEREO;
+
+            default:
+                return AudioRecord.ERROR_BAD_VALUE;
+        }
+    }
+
+    // Encodings
+    public static int sampleSizeInBytes(int encoding) {
+        switch (encoding) {
+            case AudioFormat.ENCODING_PCM_16BIT:
+                return 2;
+
+            case AudioFormat.ENCODING_PCM_FLOAT:
+                return 4;
+
+            default:
+                return 0;
+        }
+    }
+
+    public static int calcFrameSizeInBytes(int encoding, int numChannels) {
+        return sampleSizeInBytes(encoding) * numChannels;
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/README b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/README
new file mode 100644
index 0000000..2309962
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/README
@@ -0,0 +1 @@
+This code is forked from (AndroidStudio Project) <branch>vendor/box/team/audio/AudioLibApp/audiolib
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java
new file mode 100644
index 0000000..2d91acf
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java
@@ -0,0 +1,39 @@
+/*
+ * 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.audio.audiolib;
+
+/**
+ * Generates buffers of PCM data.
+ */
+public class SignalGenerator {
+    @SuppressWarnings("unused")
+    private static final String TAG = "SignalGenerator";
+
+    /**
+     * Fills a PCMFloat buffer with 1 cycle of a sine wave.
+     * NOTE: The first and last (index 0 and size-1) are filled with the
+     * sample value because WaveTableFloatFiller assumes this (to make the
+     * interpolation calculation at the end of wavetable more efficient.
+     */
+    static public void fillFloatSine(float[] buffer) {
+        int size = buffer.length;
+        float incr = ((float)Math.PI  * 2.0f) / (float)(size - 1);
+        for(int index = 0; index < size; index++) {
+            buffer[index] = (float)Math.sin(index * incr);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java
new file mode 100644
index 0000000..12f1853
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java
@@ -0,0 +1,217 @@
+/*
+ * 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.audio.audiolib;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+
+import android.util.Log;
+
+/**
+ * Plays audio data from a stream. Audio data comes from a provided AudioFiller subclass instance.
+ */
+public class StreamPlayer {
+    @SuppressWarnings("unused")
+    private static String TAG = "StreamPlayer";
+
+    private int mSampleRate;
+    private int mNumChans;
+
+    private AudioFiller mFiller;
+
+    private Thread mPlayerThread;
+
+    private AudioTrack mAudioTrack;
+    private int mNumAudioTrackFrames; // number of frames for INTERNAL AudioTrack buffer
+
+    // The Burst Buffer. This is the buffer we fill with audio and feed into the AudioTrack.
+    private int mNumBurstFrames;
+    private float[] mBurstBuffer;
+
+    private float[] mChanGains;
+    private volatile boolean mPlaying;
+
+    private AudioDeviceInfo mRoutingDevice;
+
+    public StreamPlayer() {}
+
+    public int getSampleRate() { return mSampleRate; }
+    public int getNumBurstFrames() { return mNumBurstFrames; }
+
+    public void setChanGains(float[] chanGains) {
+        mChanGains = chanGains;
+    }
+
+    public boolean isPlaying() { return mPlaying; }
+
+    private void applyChannelGains() {
+        if (mChanGains != null) {
+            int buffIndex = 0;
+            for (int frame = 0; frame < mNumBurstFrames; frame++) {
+                for (int chan = 0; chan < mNumChans; chan++) {
+                    mBurstBuffer[buffIndex++] *= mChanGains[chan];
+                }
+            }
+        }
+    }
+
+    public void setFiller(AudioFiller filler) { mFiller = filler; }
+
+    public void setRouting(AudioDeviceInfo routingDevice) {
+        mRoutingDevice = routingDevice;
+        if (mAudioTrack != null) {
+            mAudioTrack.setPreferredDevice(mRoutingDevice);
+        }
+    }
+
+    public AudioTrack getAudioTrack() { return mAudioTrack; }
+
+    private void allocBurstBuffer() {
+        mBurstBuffer = new float[mNumBurstFrames * mNumChans];
+    }
+
+    private static int calcNumBufferBytes(int sampleRate, int numChannels, int encoding) {
+        return AudioTrack.getMinBufferSize(sampleRate,
+                    AudioUtils.countToOutPositionMask(numChannels),
+                    encoding);
+    }
+
+    private static int calcNumBufferFrames(int sampleRate, int numChannels, int encoding) {
+        return calcNumBufferBytes(sampleRate, numChannels, encoding)
+                / AudioUtils.calcFrameSizeInBytes(encoding, numChannels);
+    }
+
+    public static int calcNumBurstFrames(AudioManager am) {
+        String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+        return Integer.parseInt(framesPerBuffer, 10);
+    }
+
+    public boolean open(int numChans, int sampleRate, int numBurstFrames, AudioFiller filler) {
+//        Log.i(TAG, "StreamPlayer.open(chans:" + numChans + ", rate:" + sampleRate +
+//                ", frames:" + numBurstFrames);
+
+        mNumChans = numChans;
+        mSampleRate = sampleRate;
+        mNumBurstFrames = numBurstFrames;
+
+        mNumAudioTrackFrames =
+                calcNumBufferFrames(sampleRate, numChans, AudioFormat.ENCODING_PCM_FLOAT);
+
+        mFiller = filler;
+
+        int bufferSizeInBytes = mNumAudioTrackFrames *
+                AudioUtils.calcFrameSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT, mNumChans);
+        try {
+            mAudioTrack = new AudioTrack.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+                            .setSampleRate(mSampleRate)
+                            .setChannelIndexMask(AudioUtils.countToIndexMask(mNumChans))
+                            .build())
+                    .setBufferSizeInBytes(bufferSizeInBytes)
+                    .build();
+
+            allocBurstBuffer();
+            return true;
+        }  catch (UnsupportedOperationException ex) {
+            Log.i(TAG, "Couldn't open AudioTrack: " + ex);
+            mAudioTrack = null;
+            return false;
+        }
+    }
+
+    private void waitForPlayerThreadToExit() {
+        try {
+            if (mPlayerThread != null) {
+                mPlayerThread.join();
+                mPlayerThread = null;
+            }
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void close() {
+        stop();
+
+        waitForPlayerThreadToExit();
+
+        if (mAudioTrack != null) {
+            mAudioTrack.release();
+            mAudioTrack = null;
+        }
+    }
+
+    public boolean start() {
+        if (!mPlaying && mAudioTrack != null) {
+            mPlaying = true;
+
+            waitForPlayerThreadToExit(); // just to be sure.
+
+            mPlayerThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread");
+            mPlayerThread.start();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    public void stop() {
+        mPlaying = false;
+    }
+
+    //
+    // StreamPlayerRunnable
+    //
+    private class StreamPlayerRunnable implements Runnable {
+        @Override
+        public void run() {
+            final int numBurstSamples = mNumBurstFrames * mNumChans;
+
+            mAudioTrack.play();
+            while (true) {
+                boolean playing;
+                synchronized(this) {
+                    playing = mPlaying;
+                }
+                if (!playing) {
+                    break;
+                }
+
+                mFiller.fill(mBurstBuffer, mNumBurstFrames, mNumChans);
+                if (mChanGains != null) {
+                    applyChannelGains();
+                }
+                int numSamplesWritten =
+                        mAudioTrack.write(mBurstBuffer, 0, numBurstSamples, AudioTrack.WRITE_BLOCKING);
+                if (numSamplesWritten < 0) {
+                    // error
+                    Log.i(TAG, "AudioTrack write error: " + numSamplesWritten);
+                    stop();
+                } else if (numSamplesWritten < numBurstSamples) {
+                    // end of stream
+                    Log.i(TAG, "Stream Complete.");
+                    stop();
+                }
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
new file mode 100644
index 0000000..ed25743
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
@@ -0,0 +1,225 @@
+/*
+ * 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.audio.audiolib;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+
+import android.util.Log;
+
+/**
+ * Records audio data to a stream.
+ */
+public class StreamRecorder {
+    @SuppressWarnings("unused")
+    private static final String TAG = "StreamRecorder";
+
+    // Sample Buffer
+    private float[] mBurstBuffer;
+    private int mNumBurstFrames;
+    private int mNumChannels;
+
+    // Recording attributes
+    private int mSampleRate;
+
+    // Recording state
+    Thread mRecorderThread = null;
+    private AudioRecord mAudioRecord = null;
+    private boolean mRecording = false;
+
+    private StreamRecorderListener mListener = null;
+
+    private AudioDeviceInfo mRoutingDevice = null;
+
+    public StreamRecorder() {}
+
+    public int getNumBurstFrames() { return mNumBurstFrames; }
+    public int getSampleRate() { return mSampleRate; }
+
+    /*
+     * State
+     */
+    public static int calcNumBufferBytes(int numChannels, int sampleRate, int encoding) {
+        // NOTE: Special handling of 4-channels. There is currently no AudioFormat positional
+        // constant for 4-channels of input, so in this case, calculate for 2 and double it.
+        int numBytes = 0;
+        if (numChannels == 4) {
+            numBytes = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_STEREO,
+                    encoding);
+            numBytes *= 2;
+        } else {
+            numBytes = AudioRecord.getMinBufferSize(sampleRate,
+                    AudioUtils.countToInPositionMask(numChannels), encoding);
+        }
+
+        return numBytes;
+    }
+
+    public static int calcNumBufferFrames(int numChannels, int sampleRate, int encoding) {
+        return calcNumBufferBytes(numChannels, sampleRate, encoding) /
+                AudioUtils.calcFrameSizeInBytes(encoding, numChannels);
+    }
+
+    public boolean isInitialized() {
+        return mAudioRecord != null && mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED;
+    }
+
+    public boolean isRecording() { return mRecording; }
+
+    public void setRouting(AudioDeviceInfo routingDevice) {
+        Log.i(TAG, "setRouting(" + (routingDevice != null ? routingDevice.getId() : -1) + ")");
+        mRoutingDevice = routingDevice;
+        if (mAudioRecord != null) {
+            mAudioRecord.setPreferredDevice(mRoutingDevice);
+        }
+    }
+
+    /*
+     * Accessors
+     */
+    public float[] getBurstBuffer() { return mBurstBuffer; }
+
+    public int getNumChannels() { return mNumChannels; }
+
+    /*
+     * Events
+     */
+    public void setListener(StreamRecorderListener listener) {
+        mListener = listener;
+    }
+
+    private void waitForRecorderThreadToExit() {
+        try {
+            if (mRecorderThread != null) {
+                mRecorderThread.join();
+                mRecorderThread = null;
+            }
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private boolean open_internal(int numChans, int sampleRate) {
+        Log.i(TAG, "StreamRecorder.open_internal(chans:" + numChans + ", rate:" + sampleRate);
+
+        mNumChannels = numChans;
+        mSampleRate = sampleRate;
+
+        int chanMask = AudioUtils.countToIndexMask(numChans);
+        int bufferSizeInBytes =
+            AudioRecord.getMinBufferSize(mSampleRate, chanMask, AudioFormat.ENCODING_PCM_FLOAT);
+        try {
+            mAudioRecord = new AudioRecord.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+                            .setSampleRate(mSampleRate)
+                            .setChannelIndexMask(chanMask)
+                            .build())
+                    .setBufferSizeInBytes(bufferSizeInBytes)
+                    .build();
+
+            return true;
+        } catch (UnsupportedOperationException ex) {
+            Log.i(TAG, "Couldn't open AudioRecord: " + ex);
+            mAudioRecord = null;
+            return false;
+        }
+    }
+
+    public boolean open(int numChans, int sampleRate, int numBurstFrames) {
+        boolean sucess = open_internal(numChans, sampleRate);
+        if (sucess) {
+            mNumBurstFrames = numBurstFrames;
+            mBurstBuffer = new float[mNumBurstFrames * mNumChannels];
+        }
+
+        return sucess;
+    }
+
+    public void close() {
+        stop();
+
+        waitForRecorderThreadToExit();
+
+        mAudioRecord.release();
+        mAudioRecord = null;
+    }
+
+    public boolean start() {
+        mAudioRecord.setPreferredDevice(mRoutingDevice);
+
+        if (mListener != null) {
+            mListener.sendEmptyMessage(StreamRecorderListener.MSG_START);
+        }
+
+        try {
+            mAudioRecord.startRecording();
+        } catch (IllegalStateException ex) {
+            Log.i("", "ex: " + ex);
+        }
+        mRecording = true;
+
+        waitForRecorderThreadToExit(); // just to be sure.
+
+        mRecorderThread = new Thread(new StreamRecorderRunnable(), "StreamRecorder Thread");
+        mRecorderThread.start();
+
+        return true;
+    }
+
+    public void stop() {
+        if (mRecording) {
+            mRecording = false;
+        }
+    }
+
+    /*
+     * StreamRecorderRunnable
+     */
+    private class StreamRecorderRunnable implements Runnable {
+        @Override
+        public void run() {
+            final int numBurstSamples = mNumBurstFrames * mNumChannels;
+            while (mRecording) {
+                int numReadSamples = mAudioRecord.read(
+                        mBurstBuffer, 0, numBurstSamples, AudioRecord.READ_BLOCKING);
+
+                if (numReadSamples < 0) {
+                    // error
+                    Log.i(TAG, "AudioRecord write error: " + numReadSamples);
+                    stop();
+                } else if (numReadSamples < numBurstSamples) {
+                    // got less than requested?
+                    Log.i(TAG, "AudioRecord Underflow: " + numReadSamples +
+                            " vs. " + numBurstSamples);
+                    stop();
+                }
+
+                if (mListener != null && numReadSamples == numBurstSamples) {
+                    mListener.sendEmptyMessage(StreamRecorderListener.MSG_BUFFER_FILL);
+                }
+            }
+
+            if (mListener != null) {
+                // TODO: on error or underrun we may be send bogus data.
+                mListener.sendEmptyMessage(StreamRecorderListener.MSG_STOP);
+            }
+            mAudioRecord.stop();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java
new file mode 100644
index 0000000..c542432
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java
@@ -0,0 +1,44 @@
+/*
+ * 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.audio.audiolib;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+public class StreamRecorderListener extends Handler {
+    @SuppressWarnings("unused")
+    private static final String TAG = "StreamRecorderListener";
+
+    public static final int MSG_START = 0;
+    public static final int MSG_BUFFER_FILL = 1;
+    public static final int MSG_STOP = 2;
+
+    public StreamRecorderListener(Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_START:
+            case MSG_BUFFER_FILL:
+            case MSG_STOP:
+                break;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveScopeView.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveScopeView.java
new file mode 100644
index 0000000..c52d284
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveScopeView.java
@@ -0,0 +1,189 @@
+/*
+ * 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.audio.audiolib;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class WaveScopeView extends View {
+    @SuppressWarnings("unused")
+    private static final String TAG = "WaveScopeView";
+
+    private final Paint mPaint = new Paint();
+
+    private int mBackgroundColor = Color.WHITE;
+    private int mTraceColor = Color.BLACK;
+
+    private short[] mPCM16Buffer;
+    private float[] mPCMFloatBuffer;
+
+    private int mNumChannels = 2;
+    private int mNumFrames = 0;
+
+    private float[] mPointsBuffer;
+
+    // Horrible kludge
+    private static int mCachedWidth = 0;
+
+    public WaveScopeView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void setBackgroundColor(int color) { mBackgroundColor = color; }
+
+    public void setTraceColor(int color) { mTraceColor = color; }
+
+    public void setPCM16Buff(short[] smpl16Buff, int numChans, int numFrames) {
+        mPCM16Buffer = smpl16Buff;
+        mPCMFloatBuffer = null;
+
+        mNumChannels = numChans;
+        mNumFrames = numFrames;
+
+        setupPointBuffer();
+
+        invalidate();
+    }
+
+    public void setPCMFloatBuff(float[] smplFloatBuff, int numChans, int numFrames) {
+        mPCMFloatBuffer = smplFloatBuff;
+        mPCM16Buffer = null;
+
+        mNumChannels = numChans;
+        mNumFrames = numFrames;
+
+        setupPointBuffer();
+
+        invalidate();
+    }
+
+    private void setupPointBuffer() {
+        int width = getWidth();
+
+        // Horrible kludge
+        if (width == 0) {
+            width = mCachedWidth;
+        } else {
+            mCachedWidth = width;
+        }
+
+        // Canvas.drawLines() uses 2 points (float pairs) per line-segment
+        mPointsBuffer = new float[mNumFrames * 4];
+
+        float xIncr = (float) width / (float) mNumFrames;
+
+        float X = 0;
+        int len = mPointsBuffer.length;
+        for (int pntIndex = 0; pntIndex < len;) {
+            mPointsBuffer[pntIndex] = X;
+            pntIndex += 2; // skip Y
+
+            X += xIncr;
+
+            mPointsBuffer[pntIndex] = X;
+            pntIndex += 2; // skip Y
+        }
+    }
+
+    /**
+     * Draws 1 channel of an interleaved block of SMPL16 samples.
+     * @param cvs The Canvas to draw into.
+     * @param samples The (potentially) multi-channel sample block.
+     * @param numFrames The number of FRAMES in the specified sample block.
+     * @param numChans The number of interleaved channels in the specified sample block.
+     * @param chanIndex The (0-based) index of the channel to draw.
+     * @param zeroY The Y-coordinate of sample value 0 (zero).
+     */
+    private void drawChannel16(Canvas cvs, short[] samples, int numFrames, int numChans,
+            int chanIndex, float zeroY) {
+        float yScale = getHeight() / (float) (Short.MAX_VALUE * 2 * numChans);
+        int pntIndex = 1; // of the first Y coordinate
+        float Y = zeroY;
+        int smpl = chanIndex;
+        for (int frame = 0; frame < numFrames; frame++) {
+            mPointsBuffer[pntIndex] = Y;
+            pntIndex += 2;
+
+            Y = zeroY - (samples[smpl] * yScale);
+
+            mPointsBuffer[pntIndex] = Y;
+            pntIndex += 2;
+
+            smpl += numChans;
+        }
+        cvs.drawLines(mPointsBuffer, mPaint);
+    }
+
+    /**
+     * Draws 1 channel of an interleaved block of FLOAT samples.
+     * @param cvs The Canvas to draw into.
+     * @param samples The (potentially) multi-channel sample block.
+     * @param numFrames The number of FRAMES in the specified sample block.
+     * @param numChans The number of interleaved channels in the specified sample block.
+     * @param chanIndex The (0-based) index of the channel to draw.
+     * @param zeroY The Y-coordinate of sample value 0 (zero).
+     */
+    private void drawChannelFloat(Canvas cvs, float[] samples, int numFrames, int numChans,
+            int chanIndex, float zeroY) {
+        float yScale = getHeight() / (float) (2 * numChans);
+        int pntIndex = 1; // of the first Y coordinate
+        float Y = zeroY;
+        int smpl = chanIndex;
+        for (int frame = 0; frame < numFrames; frame++) {
+            mPointsBuffer[pntIndex] = Y;
+            pntIndex += 2;
+
+            Y = zeroY - (samples[smpl] * yScale);
+
+            mPointsBuffer[pntIndex] = Y;
+            pntIndex += 2;
+
+            smpl += numChans;
+        }
+        cvs.drawLines(mPointsBuffer, mPaint);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        int height = getHeight();
+        mPaint.setColor(mBackgroundColor);
+        canvas.drawRect(0, 0, getWidth(), height, mPaint);
+
+        mPaint.setColor(mTraceColor);
+        if (mPCM16Buffer != null) {
+            float yOffset = height / (2.0f * mNumChannels);
+            float yDelta = height / (float) mNumChannels;
+            for(int channel = 0; channel < mNumChannels; channel++) {
+                drawChannel16(canvas, mPCM16Buffer, mNumFrames, mNumChannels, channel, yOffset);
+                yOffset += yDelta;
+            }
+        } else if (mPCMFloatBuffer != null) {
+            float yOffset = height / (2.0f * mNumChannels);
+            float yDelta = height / (float) mNumChannels;
+            for(int channel = 0; channel < mNumChannels; channel++) {
+                drawChannelFloat(canvas, mPCMFloatBuffer, mNumFrames, mNumChannels, channel, yOffset);
+                yOffset += yDelta;
+            }
+        }
+        // Log.i("WaveView", "onDraw() - done");
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java
new file mode 100644
index 0000000..1040eab
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java
@@ -0,0 +1,89 @@
+/*
+ * 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.audio.audiolib;
+
+/**
+ * A AudioFiller implementation for feeding data from a PCMFLOAT wavetable.
+ */
+public class WaveTableFloatFiller implements AudioFiller {
+    @SuppressWarnings("unused")
+    private static String TAG = "WaveTableFloatFiller";
+
+    private float[] mWaveTbl = null;
+    private int mNumWaveTblSamples = 0;
+    private float mSrcPhase = 0.0f;
+
+    private float mSampleRate = 48000;
+    private float mFreq = 1000; // some arbitrary frequency
+    private float mFN = 1.0f;   // The "nominal" frequency, essentially how much much of the
+                                // wave table needs to be played to get one cycle at the
+                                // sample rate. Used to calculate the phase increment
+
+    public WaveTableFloatFiller(float[] waveTbl) {
+        setWaveTable(waveTbl);
+    }
+
+    private void calcFN() {
+        mFN = mSampleRate / (float)mNumWaveTblSamples;
+    }
+
+    public void setWaveTable(float[] waveTbl) {
+        mWaveTbl = waveTbl;
+        mNumWaveTblSamples = waveTbl != null ? mWaveTbl.length - 1 : 0;
+
+        calcFN();
+    }
+
+    public void setSampleRate(float sampleRate) {
+        mSampleRate = sampleRate;
+        calcFN();
+    }
+
+    public void setFreq(float freq) {
+        mFreq = freq;
+    }
+
+    @Override
+    public void reset() {
+        mSrcPhase = 0.0f;
+    }
+
+    public int fill(float[] buffer, int numFrames, int numChans) {
+        final float phaseIncr = mFreq / mFN;
+        int outIndex = 0;
+        for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+            // 'mod' back into the waveTable
+            while (mSrcPhase >= (float)mNumWaveTblSamples) {
+                mSrcPhase -= (float)mNumWaveTblSamples;
+            }
+
+            // linear-interpolate
+            int srcIndex = (int)mSrcPhase;
+            float delta0 = mSrcPhase - (float)srcIndex;
+            float delta1 = 1.0f - delta0;
+            float value = ((mWaveTbl[srcIndex] * delta0) + (mWaveTbl[srcIndex + 1] * delta1));
+
+            for (int chanIndex = 0; chanIndex < numChans; chanIndex++) {
+                buffer[outIndex++] = value;
+            }
+
+            mSrcPhase += phaseIncr;
+        }
+
+        return numFrames;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/AudioStringsHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/AudioStringsHelper.java
new file mode 100644
index 0000000..e9acacc
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/AudioStringsHelper.java
@@ -0,0 +1,119 @@
+/*
+ * 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.audio.peripheralprofile;
+
+import android.support.annotation.NonNull;
+
+public class AudioStringsHelper {
+    // These correspond to encoding constants defined in AudioFormats.java
+    static private final String formatStrings[] = {
+            "0 Unknown Format",
+            "1 Unknown Format",
+            "2 ENCODING_PCM_16BIT",
+            "3 ENCODING_PCM_8BIT",
+            "4 ENCODING_PCM_FLOAT",
+            "5 ENCODING_AC3",
+            "6 ENCODING_E_AC3" };
+
+    // These too.
+    static private final String shortFormatStrings[] = {
+            "??? [0]",
+            "??? [1]",
+            "PCM16",
+            "PCM8",
+            "PCMFLOAT",
+            "AC3",
+            "E_AC3" };
+
+    static private String encodingToString(int fmt) {
+        return fmt < formatStrings.length ? formatStrings[fmt] : ("" + fmt + "  Unknown Format ");
+    }
+
+    static private String encodingToShortString(int fmt) {
+        return fmt < shortFormatStrings.length
+            ? shortFormatStrings[fmt]
+            : ("" + fmt + "  Unknown Format ");
+    }
+
+    static private String getRateString(int rate) {
+        if (rate % 1000 == 0) {
+            return "" + rate/1000 + "K";
+        } else {
+            return "" + (float)rate/1000 + "K";
+        }
+    }
+
+    @NonNull
+    static public String buildRatesStr(int[] rates) {
+        StringBuilder builder = new StringBuilder();
+        for(int rateIndex = 0; rateIndex < rates.length; rateIndex++) {
+            builder.append(getRateString(rates[rateIndex]));
+            if (rateIndex < rates.length - 1) {
+                builder.append(", ");
+            }
+        }
+        return builder.toString();
+    }
+
+    @NonNull
+    static public String buildEncodingsStr(int encodings[]) {
+        StringBuilder builder = new StringBuilder();
+        for(int encodingIndex = 0; encodingIndex < encodings.length; encodingIndex++) {
+            builder.append(encodingToShortString(encodings[encodingIndex]));
+            if (encodingIndex < encodings.length - 1) {
+                builder.append(", ");
+            }
+        }
+        return builder.toString();
+    }
+
+    @NonNull
+    static public String buildChannelCountsStr(int counts[]) {
+        return makeIntList(counts);
+    }
+
+    @NonNull
+    public static String makeRatesList(int[] values) {
+        return makeIntList(values);
+    }
+
+    @NonNull
+    public static String makeIntList(int[] values) {
+        StringBuilder sb = new StringBuilder();
+        for (int index = 0; index < values.length; index++) {
+            sb.append(values[index]);
+            if (index < values.length - 1) {
+                sb.append(",");
+            }
+        }
+
+        return sb.toString();
+    }
+
+    @NonNull
+    public static String makeHexList(int[] values) {
+        StringBuilder sb = new StringBuilder();
+        for (int index = 0; index < values.length; index++) {
+            sb.append("0x" + Integer.toHexString(values[index]).toUpperCase());
+            if (index < values.length - 1) {
+                sb.append(",");
+            }
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ListsHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ListsHelper.java
new file mode 100644
index 0000000..4d14347
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ListsHelper.java
@@ -0,0 +1,34 @@
+/*
+ * 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.audio.peripheralprofile;
+
+public class ListsHelper {
+    static public boolean isMatch(int[] a, int[] b) {
+        if (a.length != b.length) {
+            return false;
+        }
+
+        int len = a.length;
+        for (int index = 0; index < len; index++) {
+            if (a[index] != b[index]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java
new file mode 100644
index 0000000..4c3fbb8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java
@@ -0,0 +1,194 @@
+/*
+ * 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.audio.peripheralprofile;
+
+import android.media.AudioDeviceInfo;
+import android.support.annotation.NonNull;
+
+import com.android.cts.verifier.audio.peripheralprofile.ListsHelper;
+
+import java.io.IOException;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class PeripheralProfile extends DefaultHandler {
+    private String mProfileName;
+    private String mProfileDescription;
+
+    private String mProductName = "";    // From AudioDeviceInfo
+
+    public class ProfileAttributes {
+        public int[] mChannelCounts;
+        public int[] mChannelIndexMasks;
+        public int[] mChannelPositionMasks;
+        public int[] mEncodings;
+        public int[] mSampleRates;
+    }
+
+    ProfileAttributes mOutputAttributes;
+    ProfileAttributes mInputAttributes;
+
+    ProfileButtonAttributes mButtonAttributes;
+
+    //
+    // Accessors
+    //
+    public String getName() { return mProfileName; }
+    public String getDescription() { return mProfileDescription; }
+    public String getProductName() { return mProductName; }
+
+    public ProfileAttributes getOutputAttributes() {
+        return mOutputAttributes;
+    }
+    public ProfileAttributes getInputAttributes() {
+        return mInputAttributes;
+    }
+    public ProfileButtonAttributes getButtonAttributes() {
+        return mButtonAttributes;
+    }
+
+    @Override
+    public String toString() { return mProfileName; }
+
+    public PeripheralProfile(String profileName, String profileDescription,
+                             AudioDeviceInfo outDeviceInfo,
+                             AudioDeviceInfo inDeviceInfo,
+                             ProfileButtonAttributes buttonAttributes) {
+        mProfileName = profileName;
+        mProfileDescription = profileDescription;
+
+        if (outDeviceInfo != null) {
+            mProductName = outDeviceInfo.getProductName().toString();
+
+            mOutputAttributes = new ProfileAttributes();
+            mOutputAttributes.mChannelCounts =
+                outDeviceInfo.getChannelCounts();
+            mOutputAttributes.mChannelIndexMasks =
+                outDeviceInfo.getChannelIndexMasks();
+            mOutputAttributes.mChannelPositionMasks =
+                outDeviceInfo.getChannelMasks();
+            mOutputAttributes.mEncodings = outDeviceInfo.getEncodings();
+            mOutputAttributes.mSampleRates = outDeviceInfo.getSampleRates();
+        } else {
+            mOutputAttributes = null;
+        }
+
+        if (inDeviceInfo != null) {
+            mProductName = outDeviceInfo.getProductName().toString();
+
+            mInputAttributes = new ProfileAttributes();
+            mInputAttributes.mChannelCounts = inDeviceInfo.getChannelCounts();
+            mInputAttributes.mChannelIndexMasks = inDeviceInfo.getChannelIndexMasks();
+            mInputAttributes.mChannelPositionMasks = inDeviceInfo.getChannelMasks();
+            mInputAttributes.mEncodings = inDeviceInfo.getEncodings();
+            mInputAttributes.mSampleRates = inDeviceInfo.getSampleRates();
+        } else {
+            mInputAttributes = null;
+        }
+
+        mButtonAttributes = buttonAttributes;
+    }
+
+    public static boolean matches(ProfileAttributes attribs, AudioDeviceInfo deviceInfo) {
+        boolean match =
+            ListsHelper.isMatch(deviceInfo.getChannelCounts(), attribs.mChannelCounts) &&
+            ListsHelper.isMatch(deviceInfo.getChannelIndexMasks(), attribs.mChannelIndexMasks) &&
+            ListsHelper.isMatch(deviceInfo.getChannelMasks(), attribs.mChannelPositionMasks) &&
+            ListsHelper.isMatch(deviceInfo.getEncodings(), attribs.mEncodings) &&
+            ListsHelper.isMatch(deviceInfo.getSampleRates(), attribs.mSampleRates);
+        return match;
+    }
+
+    //
+    // Peripheral (XML) Loading
+    //
+    private static int[] parseIntList(String intList) {
+        String[] strings = intList.split(",");
+        int[] ints = new int[strings.length];
+        for (int index = 0; index < strings.length; index++) {
+            ints[index] = Integer.parseInt(strings[index]);
+        }
+        return ints;
+    }
+
+    // XML Tags
+    public static final String kTag_Profile = "PeripheralProfile";
+    public static final String kTag_OutputDevInfo = "OutputDevInfo";
+    public static final String kTag_InputDevInfo = "InputDevInfo";
+    public static final String kTag_ButtonInfo = "ButtonInfo";
+
+    // XML Attributes
+    //  - Attributes for Profile Tag
+    private static final String kAttr_ProfileName = "ProfileName";
+    private static final String kAttr_ProfileDescription = "ProfileDescription";
+    private static final String kAttr_Product = "ProductName";
+
+    //  - Attributes for DevInfo tags
+    private static final String kAttr_ChanCounts = "ChanCounts";
+    private static final String kAttr_ChanPosMasks = "ChanPosMasks";
+    private static final String kAttr_ChanIndexMasks = "ChanIndexMasks";
+    private static final String kAttr_Encodings = "Encodings";
+    private static final String kAttr_SampleRates = "SampleRates";
+    private static final String kAttr_HasBtnA = "HasBtnA";
+    private static final String kAttr_HasBtnB = "HasBtnB";
+    private static final String kAttr_HasBtnC = "HasBtnC";
+    private static final String kAttr_HasBtnD = "HasBtnD";
+
+    private void parseProfileAttributes(ProfileAttributes attribs, String elementName,
+                                        Attributes xmlAtts) {
+        attribs.mChannelCounts = parseIntList(xmlAtts.getValue(kAttr_ChanCounts));
+        attribs.mChannelPositionMasks = parseIntList(xmlAtts.getValue(kAttr_ChanPosMasks));
+        attribs.mChannelIndexMasks = parseIntList(xmlAtts.getValue(kAttr_ChanIndexMasks));
+        attribs.mEncodings = parseIntList(xmlAtts.getValue(kAttr_Encodings));
+        attribs.mSampleRates = parseIntList(xmlAtts.getValue(kAttr_SampleRates));
+    }
+
+    private void parseProfileButtons(ProfileButtonAttributes buttonAttributes, String elementName,
+                                     Attributes xmlAtts) {
+        buttonAttributes.mHasBtnA = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnA)) == 1;
+        buttonAttributes.mHasBtnB = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnB)) == 1;
+        buttonAttributes.mHasBtnC = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnC)) == 1;
+        buttonAttributes.mHasBtnD = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnD)) == 1;
+    }
+
+    //
+    // org.xml.sax.helpers.DefaultHandler overrides
+    //
+    @Override
+    public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+        if (qName.equals(kTag_Profile)) {
+            mProfileName = atts.getValue(kAttr_ProfileName);
+            mProfileDescription = atts.getValue(kAttr_ProfileDescription);
+            mProductName = atts.getValue(kAttr_Product);
+        } else if (qName.equals(kTag_OutputDevInfo)) {
+            mOutputAttributes = new ProfileAttributes();
+            parseProfileAttributes(mOutputAttributes, localName, atts);
+        } else if (qName.equals(kTag_InputDevInfo)) {
+            mInputAttributes = new ProfileAttributes();
+            parseProfileAttributes(mInputAttributes, localName, atts);
+        } else if (qName.equals(kTag_ButtonInfo)) {
+            mButtonAttributes = new ProfileButtonAttributes();
+            parseProfileButtons(mButtonAttributes, localName, atts);
+        }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) {
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java
new file mode 100644
index 0000000..ffe5e6d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java
@@ -0,0 +1,24 @@
+/*
+ * 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.audio.peripheralprofile;
+
+public class ProfileButtonAttributes {
+    public boolean mHasBtnA;
+    public boolean mHasBtnB;
+    public boolean mHasBtnC;
+    public boolean mHasBtnD;
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
new file mode 100644
index 0000000..1d585ac
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
@@ -0,0 +1,156 @@
+/*
+ * 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.audio.peripheralprofile;
+
+import android.os.Environment;
+import android.support.annotation.Nullable;
+import android.util.Xml;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+public class ProfileManager {
+    private static final String mBuiltInprofiles =
+            "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
+            "<ProfileList Version=\"1.0.0\">" +
+              "<PeripheralProfile ProfileName=\"Headset\" ProfileDescription=\"Microsoft LX-3000\" ProductName=\"USB-Audio - Microsoft LifeChat LX-3000\">" +
+                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
+                "<ButtonInfo HasBtnA=\"0\" HasBtnB=\"1\" HasBtnC=\"1\" HasBtnD=\"0\" />" +
+            "</PeripheralProfile>" +
+            "<PeripheralProfile ProfileName=\"Audio Interface\" ProfileDescription=\"Presonus AudioVox 44VSL\" ProductName=\"USB-Audio - AudioBox 44 VSL\">" +
+              "<OutputDevInfo ChanCounts=\"2,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+              "<InputDevInfo ChanCounts=\"2,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+            "</PeripheralProfile>" +
+            "<PeripheralProfile ProfileName=\"AudioBox 22VSL\" ProfileDescription=\"Presonus AudioBox 22VSL\" ProductName=\"USB-Audio - AudioBox 22 VSL\">" +
+              "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+              "<InputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+            "</PeripheralProfile>" +
+          "</ProfileList>";
+
+    // XML Tags and Attributes
+    private final static String kTag_ProfileList = "ProfileList";
+    private final static String kAttrName_Version = "Version";
+    private final static String kValueStr_Version = "1.0.0";
+
+    private final ArrayList<PeripheralProfile> mProfiles =
+        new ArrayList<PeripheralProfile>();
+
+    private PeripheralProfile mParsingProfile = null;
+
+    public boolean addProfile(PeripheralProfile profile) {
+        mProfiles.add(profile);
+
+        return true;
+    }
+
+    private class ProfileLoader extends DefaultHandler {
+        @Override
+        public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+            if (localName.equals(kTag_ProfileList)) {
+                // Maybe check the version here.
+            } else if (localName.equals(PeripheralProfile.kTag_Profile)){
+                mParsingProfile = new PeripheralProfile(null, null, null, null, null);
+                mParsingProfile.startElement(namespaceURI, localName, qName, atts);
+            } else {
+                mParsingProfile.startElement(namespaceURI, localName, qName, atts);
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) {
+            if (localName.equals(kTag_ProfileList)) {
+                // Version Checking here maybe?
+            } else if (localName.equals(PeripheralProfile.kTag_Profile)){
+                mProfiles.add(mParsingProfile);
+                mParsingProfile = null;
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return super.toString();
+    }
+
+    public boolean loadProfiles(InputStream inStream) {
+        SAXParserFactory spf = SAXParserFactory.newInstance();
+        SAXParser sp;
+        try {
+            sp = spf.newSAXParser();
+            XMLReader xr = sp.getXMLReader();
+            xr.setContentHandler(new ProfileLoader());
+            xr.parse(new InputSource(inStream));
+        } catch (ParserConfigurationException e) {
+            e.printStackTrace();
+        } catch (SAXException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return true;
+    }
+
+    public boolean loadProfiles(String profilesXML) {
+        mProfiles.clear();
+
+        return loadProfiles(new ByteArrayInputStream(profilesXML.getBytes()));
+    }
+
+    public boolean loadProfiles() {
+        return loadProfiles(mBuiltInprofiles);
+    }
+
+    //
+    // Access
+    //
+    public ArrayList<PeripheralProfile> getProfiles() { return mProfiles; }
+
+    public int getNumProfiles() {
+        return mProfiles.size();
+    }
+    public PeripheralProfile getProfile(int index) {
+        return mProfiles.get(index);
+    }
+
+    @Nullable
+    public PeripheralProfile getProfile(String productName) {
+        for(PeripheralProfile profile : mProfiles) {
+            if (productName.equals(profile.getProductName())) {
+                return profile;
+            }
+        }
+        return null;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/USBDeviceInfoHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/USBDeviceInfoHelper.java
new file mode 100644
index 0000000..54cbf53
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/USBDeviceInfoHelper.java
@@ -0,0 +1,90 @@
+/*
+ * 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.audio.peripheralprofile;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.util.Log;
+
+public class USBDeviceInfoHelper {
+    @SuppressWarnings("unused")
+    private static final String TAG = "USBDeviceInfoHelper";
+
+    private static final String kUSBPrefix = "USB-Audio - ";
+
+    // TODO - we can't treat the maximum channel count the same for inputs and outputs
+    public static int calcMaxChannelCount(AudioDeviceInfo deviceInfo) {
+        if (deviceInfo == null) {
+            return 2;   // for testing....
+        }
+
+        int maxChanCount = 0;
+        int[] counts = deviceInfo.getChannelCounts();
+        for (int chanCount : counts) {
+            if (chanCount > maxChanCount) {
+                maxChanCount = chanCount;
+            }
+        }
+        return maxChanCount;
+    }
+
+    // TODO This should be in a library module devoted to channel management, not USB.
+    public static int getPlayChanMask(AudioDeviceInfo deviceInfo) {
+        int numChans = calcMaxChannelCount(deviceInfo);
+        switch (numChans) {
+            case 1:
+                return AudioFormat.CHANNEL_OUT_MONO;
+
+            case 2:
+                return AudioFormat.CHANNEL_OUT_STEREO;
+
+            case 4:
+                return AudioFormat.CHANNEL_OUT_QUAD;
+
+            default:
+                // Huh!
+                Log.e(TAG, "getPlayChanMask() Unsupported number of channels: " + numChans);
+                return AudioFormat.CHANNEL_OUT_STEREO;
+        }
+    }
+
+    // TODO This should be in a library module devoted to channel management, not USB.
+    public static int getIndexedChanMask(int numChannels) {
+        return 0x80000000 | numChannels;
+    }
+
+    // TODO This should be in a library module devoted to channel management, not USB.
+    public static int getRecordChanMask(AudioDeviceInfo deviceInfo) {
+        int numChans = calcMaxChannelCount(deviceInfo);
+        switch (numChans) {
+            case 1:
+                return AudioFormat.CHANNEL_IN_MONO;
+
+            case 2:
+                return AudioFormat.CHANNEL_IN_STEREO;
+
+            default:
+                // Huh!
+                return AudioFormat.CHANNEL_OUT_STEREO;
+        }
+    }
+
+    public static String extractDeviceName(String productName) {
+        return productName.startsWith(kUSBPrefix)
+                ? productName.substring(kUSBPrefix.length())
+                : productName;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
old mode 100644
new mode 100755
index b2c6c60..ce68dbd
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
@@ -31,7 +31,6 @@
 import android.content.IntentFilter;
 import android.graphics.Color;
 import android.os.Bundle;
-import android.os.CountDownTimer;
 import android.util.Log;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -47,10 +46,6 @@
     private Map<Integer, Integer> mCount;
     private int[] mPowerLevel;
 
-    private TextView mTimerText;
-    private CountDownTimer mTimer;
-    private static final long REFRESH_MAC_TIME = 930000; // 15.5 min
-
     private static final int[] POWER_DBM = {-21, -15, -7, 1, 9};
 
     @Override
@@ -60,23 +55,6 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.ble_power_level_name,
                          R.string.ble_power_level_info, -1);
-        getPassButton().setEnabled(false);
-
-        mTimerText = (TextView)findViewById(R.id.ble_timer);
-        mTimer = new CountDownTimer(REFRESH_MAC_TIME, 1000) {
-            @Override
-            public void onTick(long millis) {
-                int min = (int)millis / 60000;
-                int sec = ((int)millis / 1000) % 60;
-                mTimerText.setText(min + ":" + sec);
-            }
-
-            @Override
-            public void onFinish() {
-                mTimerText.setTextColor(Color.RED);
-                mTimerText.setText("Time is up!");
-            }
-        };
 
         mRssiText = new HashMap<Integer, TextView>();
         mCountText = new HashMap<Integer, TextView>();
@@ -169,7 +147,6 @@
                         for (int i : mPowerLevel) {
                             mCount.put(i, 0);
                         }
-                        mTimer.start();
                     }
                     Integer t = mCount.get(powerLevelBit) + 1;
                     mCount.put(powerLevelBit, t);
@@ -189,10 +166,6 @@
                 case BleScannerService.BLE_PRIVACY_NEW_MAC_RECEIVE:
                      Toast.makeText(context, "New MAC address detected", Toast.LENGTH_SHORT)
                             .show();
-                     mTimerText.setTextColor(Color.GREEN);
-                     mTimerText.append("   Get new MAC address.");
-                     mTimer.cancel();
-                     getPassButton().setEnabled(true);
                      break;
             }
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
index b4f9724..7064f9f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
@@ -17,6 +17,7 @@
 package com.android.cts.verifier.managedprovisioning;
 
 import android.app.Activity;
+import android.app.NotificationChannel;
 import android.app.admin.DevicePolicyManager;
 import android.app.Dialog;
 import android.app.KeyguardManager;
@@ -156,6 +157,7 @@
     private static final String ORIGINAL_SETTINGS_NAME = "original settings";
 
     private static final int NOTIFICATION_ID = 7;
+    private static final String NOTIFICATION_CHANNEL_ID = TAG;
 
     private NotificationManager mNotificationManager;
     private Bundle mOriginalSettings;
@@ -170,7 +172,7 @@
     private ArrayList<File> mTempFiles = new ArrayList<File>();
 
     private void showNotification(int visibility) {
-        final Notification notification = new Notification.Builder(this)
+        final Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
                 .setSmallIcon(R.drawable.icon)
                 .setContentTitle(getString(R.string.provisioning_byod_notification_title))
                 .setVisibility(visibility)
@@ -197,6 +199,9 @@
         Intent intent = getIntent();
         String action = intent.getAction();
         Log.d(TAG, "ByodHelperActivity.onCreate: " + action);
+        mNotificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
 
         // we are explicitly started by {@link DeviceAdminTestReceiver} after a successful provisioning.
         if (action.equals(ACTION_PROFILE_PROVISIONED)) {
@@ -428,6 +433,7 @@
 
     @Override
     protected void onDestroy() {
+        mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
         cleanUpTempUris();
         super.onDestroy();
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 28c6cfa..e2d90f7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -404,9 +404,13 @@
     }
 
     private void uninstallHelperPackage() {
-        getPackageManager().getPackageInstaller().uninstall(HELPER_APP_PKG,
-                PendingIntent.getBroadcast(this, 0, new Intent(ACTION_UNINSTALL_COMPLETE), 0)
-                        .getIntentSender());
+        try {
+            getPackageManager().getPackageInstaller().uninstall(HELPER_APP_PKG,
+                    PendingIntent.getBroadcast(this, 0, new Intent(ACTION_UNINSTALL_COMPLETE), 0)
+                            .getIntentSender());
+        } catch (IllegalArgumentException e) {
+            // The package is not installed: that's fine
+        }
     }
 
     private void clearAllPolicies() throws Exception {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index 4e945e5..3949ed9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -293,6 +293,7 @@
                 PolicyTransparencyTestListActivity.class);
         policyTransparencyTestIntent.putExtra(
                 PolicyTransparencyTestListActivity.EXTRA_IS_DEVICE_OWNER, true);
+        // So that PolicyTransparencyTestListActivity knows which test to update with the result:
         policyTransparencyTestIntent.putExtra(
                 PolicyTransparencyTestActivity.EXTRA_TEST_ID, POLICY_TRANSPARENCY_TEST_ID);
         adapter.add(createTestItem(this, POLICY_TRANSPARENCY_TEST_ID,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
index 5eaf862..0f57b87 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
@@ -45,9 +45,13 @@
         AdapterView.OnItemSelectedListener {
     public static final String ACTION_SHOW_POLICY_TRANSPARENCY_TEST =
             "com.android.cts.verifier.managedprovisioning.action.SHOW_POLICY_TRANSPARENCY_TEST";
+
+    // Identifies a test to perform. Type String. The possible values are the ones underneath.
     public static final String EXTRA_TEST =
             "com.android.cts.verifier.managedprovisioning.extra.TEST";
 
+    // In this case: should also contain an extra
+    // {@link CommandReceiverActivity.EXTRA_USER_RESTRICTION}
     public static final String TEST_CHECK_USER_RESTRICTION = "check-user-restriction";
     public static final String TEST_CHECK_AUTO_TIME_REQUIRED = "check-auto-time-required";
     public static final String TEST_CHECK_KEYGURAD_UNREDACTED_NOTIFICATION =
@@ -63,6 +67,8 @@
             "com.android.cts.verifier.managedprovisioning.extra.SETTINGS_INTENT_ACTION";
     public static final String EXTRA_TITLE =
             "com.android.cts.verifier.managedprovisioning.extra.TITLE";
+    // Identifies the test in the calling activity. We will set the result for this test.
+    // Type: String
     public static final String EXTRA_TEST_ID =
             "com.android.cts.verifier.managedprovisioning.extra.TEST_ID";
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java
index c5b4a93..fb7cc12 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java
@@ -34,6 +34,8 @@
 
 /**
  * Test class to verify transparency for policies enforced by device/profile owner.
+ * Expects to be passed an extra: {@link PolicyTransparencyTestActivity#EXTRA_TEST_ID} to identify
+ * which test in the calling activity should be updated.
  */
 public class PolicyTransparencyTestListActivity extends PassFailButtons.TestListActivity
         implements View.OnClickListener {
@@ -42,8 +44,13 @@
     public static final String EXTRA_IS_DEVICE_OWNER =
             "com.android.cts.verifier.managedprovisioning.extra.IS_DEVICE_OWNER";
 
+    // Pairs of:
+    // - An intent to start PolicyTransparencyTestActivity
+    // - a label to show the user.
+    // These contain all the policies except for the user restriction ones
     private static final Pair<Intent, Integer>[] POLICIES;
     static {
+        // names of the tests
         final String[] policyTests = new String[] {
             PolicyTransparencyTestActivity.TEST_CHECK_AUTO_TIME_REQUIRED,
             PolicyTransparencyTestActivity.TEST_CHECK_KEYGURAD_UNREDACTED_NOTIFICATION,
@@ -87,6 +94,7 @@
         }
     }
 
+    // List of names of test that are also valid for PO
     private static final ArrayList<String> ALSO_VALID_FOR_PO = new ArrayList<String>();
     static {
         ALSO_VALID_FOR_PO.add(
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
index 935a1b8..968aa5b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
@@ -207,6 +207,10 @@
                 return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
             case UserManager.DISALLOW_SHARE_LOCATION:
                 return pm.hasSystemFeature(PackageManager.FEATURE_LOCATION);
+            case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES:
+                return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
+            case UserManager.DISALLOW_CONFIG_CREDENTIALS:
+                return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
             default:
                 return true;
         }
@@ -222,4 +226,4 @@
             this.intentAction = intentAction;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java
index 17e83c1..7a37b85 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.widget.Toast;
 import android.content.ActivityNotFoundException;
@@ -68,7 +69,9 @@
     static void showBugreportNotification(Context context, String msg, int notificationId) {
         NotificationManager mNotificationManager =
                 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-        Notification notification = new Notification.Builder(context)
+        mNotificationManager.createNotificationChannel(
+                new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_DEFAULT));
+        Notification notification = new Notification.Builder(context, TAG)
                 .setSmallIcon(R.drawable.icon)
                 .setContentTitle(context.getString(
                         R.string.device_owner_requesting_bugreport_tests))
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
index 9d9a4bc..0355cb4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
@@ -21,6 +21,8 @@
 import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.content.ContentProviderOperation;
 import android.content.OperationApplicationException;
 import android.database.Cursor;
@@ -30,6 +32,7 @@
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.util.Log;
 import android.view.View;
@@ -47,6 +50,8 @@
         extends InteractiveVerifierActivity {
     private static final String TAG = "NoListenerAttentionVerifier";
 
+    private static final String NOTIFICATION_CHANNEL_ID = TAG;
+    private static final String NOTIFICATION_CHANNEL_ID_NOISY = TAG + "/noisy";
     private static final String ALICE = "Alice";
     private static final String ALICE_PHONE = "+16175551212";
     private static final String ALICE_EMAIL = "alice@_foo._bar";
@@ -106,6 +111,23 @@
         return tests;
     }
 
+    private void createChannels() {
+        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+        mNm.createNotificationChannel(channel);
+        NotificationChannel noisyChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_NOISY,
+                NOTIFICATION_CHANNEL_ID_NOISY, NotificationManager.IMPORTANCE_HIGH);
+        noisyChannel.enableVibration(true);
+        noisyChannel.setSound(
+                Settings.System.DEFAULT_RINGTONE_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT);
+        mNm.createNotificationChannel(noisyChannel);
+    }
+
+    private void deleteChannels() {
+        mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+        mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_NOISY);
+    }
+
     // Tests
 
     protected class InsertContactsTest extends InteractiveTestCase {
@@ -213,6 +235,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             sendNotifications(MODE_URI, false, false);
             status = READY;
             // wait for notifications to move through the system
@@ -267,6 +290,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -305,6 +329,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             sendNotifications(MODE_URI, false, false);
             status = READY;
             // wait for notifications to move through the system
@@ -358,6 +383,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -395,6 +421,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             sendNotifications(MODE_URI, false, false);
             status = READY;
             // wait for notifications to move through the system
@@ -448,6 +475,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -462,6 +490,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             sendNotifications(MODE_NONE, false, false);
             status = READY;
             // wait for notifications to move through the system
@@ -492,6 +521,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -506,6 +536,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             sendNotifications(MODE_NONE, true, false);
             status = READY;
             // wait for notifications to move through the system
@@ -536,6 +567,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -552,6 +584,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             // send B & C noisy
             sendNotifications(SEND_B | SEND_C, MODE_NONE, false, true);
             status = READY;
@@ -601,6 +634,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -615,6 +649,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             sendNotifications(MODE_NONE, true, false);
             status = READY;
             // wait for notifications to move through the system
@@ -668,6 +703,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -682,6 +718,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             sendNotifications(MODE_URI, false, false);
             status = READY;
             // wait for notifications to move through the system
@@ -712,6 +749,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -726,6 +764,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             sendNotifications(MODE_EMAIL, false, false);
             status = READY;
             // wait for notifications to move through the system
@@ -756,6 +795,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -770,6 +810,7 @@
 
         @Override
         void setUp() {
+            createChannels();
             sendNotifications(MODE_PHONE, false, false);
             status = READY;
             // wait for notifications to move through the system
@@ -800,6 +841,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannels();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -826,39 +868,39 @@
         int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
         int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT;
 
+        final String channelId = noisy ? NOTIFICATION_CHANNEL_ID_NOISY : NOTIFICATION_CHANNEL_ID;
+
         if ((which & SEND_B) != 0) {
-            Notification.Builder bob = new Notification.Builder(mContext)
+            Notification.Builder bob = new Notification.Builder(mContext, channelId)
                     .setContentTitle(BOB)
                     .setContentText(BOB)
                     .setSmallIcon(R.drawable.ic_stat_bob)
                     .setPriority(priorityB)
                     .setCategory(Notification.CATEGORY_MESSAGE)
                     .setWhen(whenB);
-            bob.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
             addPerson(uriMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL);
             mNm.notify(BOB, NOTIFICATION_ID + 2, bob.build());
         }
         if ((which & SEND_C) != 0) {
-            Notification.Builder charlie = new Notification.Builder(mContext)
-                    .setContentTitle(CHARLIE)
-                    .setContentText(CHARLIE)
-                    .setSmallIcon(R.drawable.ic_stat_charlie)
-                    .setPriority(priorityC)
-                    .setCategory(Notification.CATEGORY_MESSAGE)
-                    .setWhen(whenC);
-            charlie.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
+            Notification.Builder charlie =
+                    new Notification.Builder(mContext, channelId)
+                            .setContentTitle(CHARLIE)
+                            .setContentText(CHARLIE)
+                            .setSmallIcon(R.drawable.ic_stat_charlie)
+                            .setPriority(priorityC)
+                            .setCategory(Notification.CATEGORY_MESSAGE)
+                            .setWhen(whenC);
             addPerson(uriMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL);
             mNm.notify(CHARLIE, NOTIFICATION_ID + 3, charlie.build());
         }
         if ((which & SEND_A) != 0) {
-            Notification.Builder alice = new Notification.Builder(mContext)
+            Notification.Builder alice = new Notification.Builder(mContext, channelId)
                     .setContentTitle(ALICE)
                     .setContentText(ALICE)
                     .setSmallIcon(R.drawable.ic_stat_alice)
                     .setPriority(priorityA)
                     .setCategory(Notification.CATEGORY_MESSAGE)
                     .setWhen(whenA);
-            alice.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
             addPerson(uriMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL);
             mNm.notify(ALICE, NOTIFICATION_ID + 1, alice.build());
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
index 36a502c..5150a26 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
@@ -40,7 +40,7 @@
 import java.util.Set;
 
 public class MockAssistant extends NotificationAssistantService {
-    static final String TAG = "MockListener";
+    static final String TAG = "MockAssistant";
 
     static final String SERVICE_BASE = "android.service.notification.cts.";
     static final String SERVICE_CHECK = SERVICE_BASE + "SERVICE_CHECK";
@@ -318,8 +318,7 @@
     }
 
     @Override
-    public Adjustment onNotificationEnqueued(StatusBarNotification sbn, int importance,
-            boolean user) {
+    public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
         if (!mTestPackages.contains(sbn.getPackageName())) { return null; }
         Log.d(TAG, "enqueued: " + sbn.getTag());
         mEnqueued.add(sbn.getTag());
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
index 540240d..191d0f1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
@@ -71,6 +71,9 @@
     private static final String THIS_PKG = "com.android.cts.verifier";
     private static final String OTHER_PKG = "android";
 
+    public static final String ORIGINAL_CHANNEL_ID = TAG + ": original";
+    public static final String NEW_CHANNEL_ID = TAG + ": new";
+
     private String mTag1;
     private String mTag2;
     private String mTag3;
@@ -126,12 +129,7 @@
     }
 
     @SuppressLint("NewApi")
-    private void sendNotifications() {
-        sendNotifications(null);
-    }
-
-    @SuppressLint("NewApi")
-    private void sendNotifications(NotificationChannel channel) {
+    private void sendNotifications(String channelId) {
         mTag1 = UUID.randomUUID().toString();
         mTag2 = UUID.randomUUID().toString();
         mTag3 = UUID.randomUUID().toString();
@@ -152,7 +150,7 @@
 
         mPackageString = "com.android.cts.verifier";
 
-        Notification n1 = new Notification.Builder(mContext)
+        Notification n1 = new Notification.Builder(mContext, channelId)
                 .setContentTitle("One")
                 .setSortKey(Adjustment.KEY_CHANNEL_ID)
                 .setContentText(mTag1.toString())
@@ -161,13 +159,11 @@
                 .setWhen(mWhen1)
                 .setDeleteIntent(makeIntent(1, mTag1))
                 .setOnlyAlertOnce(true)
-                .setChannel(channel == null ? NotificationChannel.DEFAULT_CHANNEL_ID
-                        : channel.getId())
                 .build();
         mNm.notify(mTag1, mId1, n1);
         mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
 
-        Notification n2 = new Notification.Builder(mContext)
+        Notification n2 = new Notification.Builder(mContext, channelId)
                 .setContentTitle("Two")
                 .setSortKey(Adjustment.KEY_PEOPLE)
                 .setContentText(mTag2.toString())
@@ -176,13 +172,11 @@
                 .setWhen(mWhen2)
                 .setDeleteIntent(makeIntent(2, mTag2))
                 .setAutoCancel(true)
-                .setChannel(channel == null ? NotificationChannel.DEFAULT_CHANNEL_ID
-                        : channel.getId())
                 .build();
         mNm.notify(mTag2, mId2, n2);
         mFlag2 = Notification.FLAG_AUTO_CANCEL;
 
-        Notification n3 = new Notification.Builder(mContext)
+        Notification n3 = new Notification.Builder(mContext, channelId)
                 .setContentTitle("Three")
                 .setSortKey(Adjustment.KEY_SNOOZE_CRITERIA)
                 .setContentText(mTag3.toString())
@@ -192,13 +186,34 @@
                 .setDeleteIntent(makeIntent(3, mTag3))
                 .setAutoCancel(true)
                 .setOnlyAlertOnce(true)
-                .setChannel(channel == null ? NotificationChannel.DEFAULT_CHANNEL_ID
-                        : channel.getId())
                 .build();
         mNm.notify(mTag3, mId3, n3);
         mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
     }
 
+    private void createChannels() {
+        try {
+            NotificationChannel newChannel = new NotificationChannel(
+                    NEW_CHANNEL_ID, NEW_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+            mNm.createNotificationChannel(newChannel);
+        } catch (Exception e) {
+            Log.e(TAG, "failed to create channel", e);
+        }
+        try {
+            NotificationChannel originalChannel = new NotificationChannel(ORIGINAL_CHANNEL_ID,
+                    ORIGINAL_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+            mNm.createNotificationChannel(originalChannel);
+        } catch (Exception e) {
+            Log.e(TAG, "failed to create channel", e);
+        }
+    }
+
+    private void deleteChannels() {
+        mNm.cancelAll();
+        mNm.deleteNotificationChannel(ORIGINAL_CHANNEL_ID);
+        mNm.deleteNotificationChannel(NEW_CHANNEL_ID);
+    }
+
     // Tests
 
     protected class IsEnabledTest extends InteractiveTestCase {
@@ -278,13 +293,19 @@
 
         @Override
         void setUp() {
-            sendNotifications();
+            createChannels();
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             // wait for notifications to move through the system
             delay();
         }
 
         @Override
+        void tearDown() {
+            deleteChannels();
+        }
+
+        @Override
         void test() {
             MockAssistant.probeListenerEnqueued(mContext,
                     new StringListResultCatcher() {
@@ -307,18 +328,23 @@
         @Override
         View inflate(ViewGroup parent) {
             return createAutoItem(parent, R.string.nls_note_received);
-
         }
 
         @Override
         void setUp() {
-            sendNotifications();
+            createChannels();
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             // wait for notifications to move through the system
             delay();
         }
 
         @Override
+        void tearDown() {
+            deleteChannels();
+        }
+
+        @Override
         void test() {
             MockAssistant.probeListenerPosted(mContext,
                     new StringListResultCatcher() {
@@ -422,7 +448,8 @@
 
         @Override
         void setUp() {
-            sendNotifications();
+            createChannels();
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             delay();
         }
@@ -455,7 +482,7 @@
 
         @Override
         void tearDown() {
-            mNm.cancelAll();
+            deleteChannels();
             sleep(1000);
             MockAssistant.resetListenerData(mContext);
             delay();
@@ -470,7 +497,8 @@
 
         @Override
         void setUp() {
-            sendNotifications();
+            createChannels();
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             delay();
         }
@@ -509,7 +537,7 @@
 
         @Override
         void tearDown() {
-            mNm.cancelAll();
+            deleteChannels();
             sleep(1000);
             MockAssistant.resetListenerData(mContext);
             delay();
@@ -524,7 +552,8 @@
 
         @Override
         void setUp() {
-            sendNotifications();
+            createChannels();
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             delay();
         }
@@ -557,7 +586,7 @@
 
         @Override
         void tearDown() {
-            mNm.cancelAll();
+            deleteChannels();
             sleep(1000);
             MockAssistant.resetListenerData(mContext);
             delay();
@@ -634,7 +663,8 @@
 
         @Override
         void setUp() {
-            sendNotifications();
+            createChannels();
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             delay();
         }
@@ -659,7 +689,7 @@
 
         @Override
         void tearDown() {
-            mNm.cancelAll();
+            deleteChannels();
             sleep(1000);
             MockAssistant.resetListenerData(mContext);
             delay();
@@ -675,7 +705,8 @@
 
         @Override
         void setUp() {
-            sendNotifications();
+            createChannels();
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             delay();
         }
@@ -700,7 +731,7 @@
 
         @Override
         void tearDown() {
-            mNm.cancelAll();
+            deleteChannels();
             sleep(1000);
             MockAssistant.resetListenerData(mContext);
             delay();
@@ -710,10 +741,6 @@
     private class AdjustNotificationTest extends InteractiveTestCase {
         private ArrayList<String> people;
         private ArrayList<SnoozeCriterion> snooze;
-        private NotificationChannel originalChannel = new NotificationChannel("original", "new",
-                NotificationManager.IMPORTANCE_LOW);
-        private NotificationChannel newChannel = new NotificationChannel("new", "new",
-                NotificationManager.IMPORTANCE_LOW);
         private Map<String, Bundle> adjustments;
 
         @Override
@@ -723,22 +750,13 @@
 
         @Override
         void setUp() {
-            try {
-                mNm.createNotificationChannel(newChannel);
-            } catch (Exception e) {
-                Log.e(TAG, "failed to create channel", e);
-            }
-            try {
-                mNm.createNotificationChannel(originalChannel);
-            } catch (Exception e) {
-                Log.e(TAG, "failed to create channel", e);
-            }
+            createChannels();
             adjustments = getAdjustments();
             snooze = adjustments.get(Adjustment.KEY_SNOOZE_CRITERIA).getParcelableArrayList(
                     Adjustment.KEY_SNOOZE_CRITERIA);
             people = adjustments.get(Adjustment.KEY_PEOPLE).getStringArrayList(
                     Adjustment.KEY_PEOPLE);
-            sendNotifications(originalChannel);
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             delay();
         }
@@ -785,7 +803,7 @@
                                     String tag = payload.getString(KEY_TAG);
                                     if (mTag1.equals(tag)) {
                                         found.add(mTag1);
-                                        pass &= checkEquals(newChannel.getId(),
+                                        pass &= checkEquals(NEW_CHANNEL_ID,
                                                 ((NotificationChannel) payload.getParcelable(
                                                         KEY_CHANNEL)).getId(),
                                                 "data integrity test: notification channel ("
@@ -800,7 +818,7 @@
                                                         + "%s, %s)");
                                     } else if (mTag2.equals(tag)) {
                                         found.add(mTag2);
-                                        pass &= checkEquals(originalChannel.getId(),
+                                        pass &= checkEquals(ORIGINAL_CHANNEL_ID,
                                                 ((NotificationChannel) payload.getParcelable(
                                                         KEY_CHANNEL)).getId(),
                                                 "data integrity test: notification channel ("
@@ -815,7 +833,7 @@
                                                         + "%s, %s)");
                                     } else if (mTag3.equals(tag)) {
                                         found.add(mTag3);
-                                        pass &= checkEquals(originalChannel.getId(),
+                                        pass &= checkEquals(ORIGINAL_CHANNEL_ID,
                                                 ((NotificationChannel) payload.getParcelable(
                                                         KEY_CHANNEL)).getId(),
                                                 "data integrity test: notification channel ("
@@ -845,9 +863,7 @@
 
         @Override
         void tearDown() {
-            mNm.cancelAll();
-            mNm.deleteNotificationChannel(originalChannel.getId());
-            mNm.deleteNotificationChannel(newChannel.getId());
+            deleteChannels();
             sleep(1000);
             MockAssistant.resetListenerData(mContext);
             delay();
@@ -857,10 +873,6 @@
     private class AdjustEnqueuedNotificationTest extends InteractiveTestCase {
         private ArrayList<String> people;
         private ArrayList<SnoozeCriterion> snooze;
-        private NotificationChannel originalChannel = new NotificationChannel("original", "new",
-                NotificationManager.IMPORTANCE_LOW);
-        private NotificationChannel newChannel = new NotificationChannel("new", "new",
-                NotificationManager.IMPORTANCE_LOW);
         private Map<String, Bundle> adjustments;
 
         @Override
@@ -871,22 +883,13 @@
         @Override
         void setUp() {
             MockAssistant.adjustEnqueue(mContext);
-            try {
-                mNm.createNotificationChannel(newChannel);
-            } catch (Exception e) {
-                Log.e(TAG, "failed to create channel", e);
-            }
-            try {
-                mNm.createNotificationChannel(originalChannel);
-            } catch (Exception e) {
-                Log.e(TAG, "failed to create channel", e);
-            }
+            createChannels();
             adjustments = getAdjustments();
             snooze = adjustments.get(Adjustment.KEY_SNOOZE_CRITERIA).getParcelableArrayList(
                     Adjustment.KEY_SNOOZE_CRITERIA);
             people = adjustments.get(Adjustment.KEY_PEOPLE).getStringArrayList(
                     Adjustment.KEY_PEOPLE);
-            sendNotifications(originalChannel);
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             delay();
         }
@@ -927,7 +930,7 @@
                                     String tag = payload.getString(KEY_TAG);
                                     if (mTag1.equals(tag)) {
                                         found.add(mTag1);
-                                        pass &= checkEquals(newChannel.getId(),
+                                        pass &= checkEquals(NEW_CHANNEL_ID,
                                                 ((NotificationChannel) payload.getParcelable(
                                                         KEY_CHANNEL)).getId(),
                                                 "data integrity test: notification channel ("
@@ -942,7 +945,7 @@
                                                         + "%s, %s)");
                                     } else if (mTag2.equals(tag)) {
                                         found.add(mTag2);
-                                        pass &= checkEquals(originalChannel.getId(),
+                                        pass &= checkEquals(ORIGINAL_CHANNEL_ID,
                                                 ((NotificationChannel) payload.getParcelable(
                                                         KEY_CHANNEL)).getId(),
                                                 "data integrity test: notification channel ("
@@ -957,7 +960,7 @@
                                                         + "%s, %s)");
                                     } else if (mTag3.equals(tag)) {
                                         found.add(mTag3);
-                                        pass &= checkEquals(originalChannel.getId(),
+                                        pass &= checkEquals(ORIGINAL_CHANNEL_ID,
                                                 ((NotificationChannel) payload.getParcelable(
                                                         KEY_CHANNEL)).getId(),
                                                 "data integrity test: notification channel ("
@@ -987,9 +990,7 @@
 
         @Override
         void tearDown() {
-            mNm.cancelAll();
-            mNm.deleteNotificationChannel(originalChannel.getId());
-            mNm.deleteNotificationChannel(newChannel.getId());
+            deleteChannels();
             sleep(1000);
             MockAssistant.resetListenerData(mContext);
             delay();
@@ -1123,8 +1124,18 @@
                                 if (result == null || result.size() <= 1) {
                                     status = PASS;
                                 } else {
-                                    logFail();
-                                    status = FAIL;
+                                    boolean fail = false;
+                                    for (Parcelable payloadData : result) {
+                                        NotificationChannel payload =
+                                                (NotificationChannel) payloadData;
+                                        fail |= compareChannels(channel, payload);
+                                    }
+                                    if (fail) {
+                                        logFail();
+                                        status = FAIL;
+                                    } else {
+                                        status = PASS;
+                                    }
                                 }
                                 next();
                             }
@@ -1157,8 +1168,8 @@
         @Override
         void setUp() {
             updatedChannel.setVibrationPattern(new long[] {467, 2478, 24738});
-            updatedChannel.setSound(new Uri.Builder().appendPath("sound").build());
-            updatedChannel.setLights(true);
+            updatedChannel.setSound(new Uri.Builder().appendPath("sound").build(), null);
+            updatedChannel.enableLights(true);
             updatedChannel.enableVibration(true);
             updatedChannel.setBypassDnd(true);
             updatedChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
@@ -1243,7 +1254,7 @@
             } catch (Exception e) {
                 Log.e(TAG, "failed to create channel", e);
             }
-            sendNotifications(channel);
+            sendNotifications(channel.getId());
             status = READY;
             delay(6000);
         }
@@ -1308,7 +1319,8 @@
 
         @Override
         void setUp() {
-            sendNotifications();
+            createChannels();
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             delay();
         }
@@ -1381,6 +1393,7 @@
             mNm.cancel(mTag1, mId1);
             mNm.cancel(mTag2, mId2);
             mNm.cancel(mTag2, mId3);
+            deleteChannels();
             MockAssistant.resetListenerData(mContext);
             delay();
         }
@@ -1399,7 +1412,8 @@
 
         @Override
         void setUp() {
-            sendNotifications();
+            createChannels();
+            sendNotifications(ORIGINAL_CHANNEL_ID);
             status = READY;
             delay();
         }
@@ -1457,7 +1471,7 @@
 
         @Override
         void tearDown() {
-            mNm.cancelAll();
+            deleteChannels();
             MockAssistant.resetListenerData(mContext);
             delay();
         }
@@ -1481,6 +1495,7 @@
         pass &= checkEquals(expected.getSound(), actual.getSound(), msg);
         pass &= checkEquals(expected.canBypassDnd(), actual.canBypassDnd(), msg);
         pass &= checkEquals(expected.getVibrationPattern(), actual.getVibrationPattern(), msg);
+        pass &= checkEquals(expected.getAudioAttributes(), actual.getAudioAttributes(), msg);
         return pass;
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 38fc5bd..425b36f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -19,6 +19,8 @@
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.provider.Settings.Secure;
 import android.util.Log;
 import android.view.View;
@@ -40,6 +42,7 @@
 public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
         implements Runnable {
     private static final String TAG = "NoListenerVerifier";
+    private static final String NOTIFICATION_CHANNEL_ID = TAG;
 
     private String mTag1;
     private String mTag2;
@@ -91,6 +94,16 @@
         return tests;
     }
 
+    private void createChannel() {
+        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+        mNm.createNotificationChannel(channel);
+    }
+
+    private void deleteChannel() {
+        mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+    }
+
     @SuppressLint("NewApi")
     private void sendNotifications() {
         mTag1 = UUID.randomUUID().toString();
@@ -113,7 +126,7 @@
 
         mPackageString = "com.android.cts.verifier";
 
-        Notification n1 = new Notification.Builder(mContext)
+        Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                 .setContentTitle("ClearTest 1")
                 .setContentText(mTag1.toString())
                 .setPriority(Notification.PRIORITY_LOW)
@@ -125,7 +138,7 @@
         mNm.notify(mTag1, mId1, n1);
         mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
 
-        Notification n2 = new Notification.Builder(mContext)
+        Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                 .setContentTitle("ClearTest 2")
                 .setContentText(mTag2.toString())
                 .setPriority(Notification.PRIORITY_HIGH)
@@ -137,7 +150,7 @@
         mNm.notify(mTag2, mId2, n2);
         mFlag2 = Notification.FLAG_AUTO_CANCEL;
 
-        Notification n3 = new Notification.Builder(mContext)
+        Notification n3 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                 .setContentTitle("ClearTest 3")
                 .setContentText(mTag3.toString())
                 .setPriority(Notification.PRIORITY_LOW)
@@ -162,6 +175,7 @@
 
         @Override
         void setUp() {
+            createChannel();
             sendNotifications();
             status = READY;
             // wait for notifications to move through the system
@@ -169,6 +183,11 @@
         }
 
         @Override
+        void tearDown() {
+            deleteChannel();
+        }
+
+        @Override
         void test() {
             MockListener.probeListenerPosted(mContext,
                     new MockListener.StringListResultCatcher() {
@@ -276,6 +295,7 @@
 
         @Override
         void setUp() {
+            createChannel();
             sendNotifications();
             status = READY;
             delay();
@@ -310,6 +330,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannel();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -323,6 +344,7 @@
 
         @Override
         void setUp() {
+            createChannel();
             sendNotifications();
             status = READY;
             delay();
@@ -368,6 +390,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannel();
             MockAssistant.resetListenerData(mContext);
             delay();
         }
@@ -381,6 +404,7 @@
 
         @Override
         void setUp() {
+            createChannel();
             sendNotifications();
             status = READY;
             delay();
@@ -415,6 +439,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannel();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -500,6 +525,7 @@
 
         @Override
         void setUp() {
+            createChannel();
             sendNotifications();
             status = READY;
             delay();
@@ -526,6 +552,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannel();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -678,6 +705,7 @@
 
         @Override
         void setUp() {
+            createChannel();
             sendNotifications();
             status = READY;
             state = READY_TO_SNOOZE;
@@ -747,6 +775,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannel();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -765,6 +794,7 @@
 
         @Override
         void setUp() {
+            createChannel();
             sendNotifications();
             status = READY;
             state = READY_TO_SNOOZE;
@@ -837,6 +867,7 @@
         @Override
         void tearDown() {
             mNm.cancelAll();
+            deleteChannel();
             MockListener.resetListenerData(mContext);
             delay();
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
index b40ecc6..150c21f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
@@ -17,6 +17,8 @@
 package com.android.cts.verifier.notifications;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.util.Log;
@@ -39,6 +41,7 @@
     private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
     private static final String EXTRA_ID = "ID";
     private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
+    private static final String NOTIFICATION_CHANNEL_ID = "PackagePriorityVerifierActivity";
     static final String NOTIFICATION_BOT_PACKAGE = "com.android.cts.robot";
     private CharSequence mAppLabel;
 
@@ -68,6 +71,16 @@
         return tests;
     }
 
+    private void createChannel() {
+        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+        mNm.createNotificationChannel(channel);
+    }
+
+    private void deleteChannel() {
+        mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+    }
+
     // Tests
 
     /** Make sure the helper package is installed. */
@@ -155,6 +168,7 @@
 
         @Override
         void setUp() {
+            createChannel();
             sendNotifications();
             status = READY;
             // wait for notifications to move through the system
@@ -184,6 +198,7 @@
         @Override
         void tearDown() {
             cancelNotifications();
+            deleteChannel();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -200,6 +215,7 @@
 
         @Override
         void setUp() {
+            createChannel();
             sendNotifications();
             status = READY;
             // wait for notifications to move through the system
@@ -229,6 +245,7 @@
         @Override
         void tearDown() {
             cancelNotifications();
+            deleteChannel();
             MockListener.resetListenerData(mContext);
             delay();
         }
@@ -237,7 +254,7 @@
 
     private void sendNotifications() {
         // post ours first, with an explicit time in the past to avoid any races.
-        Notification.Builder alice = new Notification.Builder(mContext)
+        Notification.Builder alice = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                 .setContentTitle("alice title")
                 .setContentText("alice content")
                 .setSmallIcon(R.drawable.ic_stat_alice)
@@ -246,7 +263,7 @@
         mNm.notify(0, alice.build());
 
         // then post theirs, so it should be higher by default due to recency
-        Notification.Builder bob = new Notification.Builder(mContext)
+        Notification.Builder bob = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                 .setContentTitle("bob title")
                 .setContentText("bob content")
                 .setSmallIcon(android.R.drawable.stat_notify_error) // must be global resource
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/os/FileUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/os/FileUtils.java
deleted file mode 100644
index 5633c16..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/os/FileUtils.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2010 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.os;
-
-/** Bits and pieces copied from hidden API of android.os.FileUtils. */
-public class FileUtils {
-
-    private static final int S_IFSOCK = 0140000;
-    private static final int S_IFLNK = 0120000;
-    private static final int S_IFREG = 0100000;
-    private static final int S_IFBLK = 0060000;
-    private static final int S_IFDIR = 0040000;
-    private static final int S_IFCHR = 0020000;
-    private static final int S_IFIFO = 0010000;
-
-    private static final int S_ISUID = 0004000;
-    private static final int S_ISGID = 0002000;
-    private static final int S_ISVTX = 0001000;
-
-    private static final int S_IRUSR = 00400;
-    private static final int S_IWUSR = 00200;
-    private static final int S_IXUSR = 00100;
-
-    private static final int S_IRGRP = 00040;
-    private static final int S_IWGRP = 00020;
-    private static final int S_IXGRP = 00010;
-
-    private static final int S_IROTH = 00004;
-    private static final int S_IWOTH = 00002;
-    private static final int S_IXOTH = 00001;
-
-    static {
-        System.loadLibrary("ctsverifier_jni");
-    }
-
-    public static class FileStatus {
-
-        private int dev;
-        private int ino;
-        private int mode;
-        private int nlink;
-        private int uid;
-        private int gid;
-        private int rdev;
-        private long size;
-        private int blksize;
-        private long blocks;
-        private long atime;
-        private long mtime;
-        private long ctime;
-        private boolean executable;
-
-        public int getUid() {
-            return uid;
-        }
-
-        public int getGid() {
-            return gid;
-        }
-
-        public int getMode() {
-            return mode;
-        }
-
-        public boolean isDirectory() {
-            return hasModeFlag(mode, S_IFDIR);
-        }
-
-        public boolean isSymbolicLink() {
-            return hasModeFlag(mode, S_IFLNK);
-        }
-
-        public boolean isSetUid() {
-            return hasModeFlag(mode, S_ISUID);
-        }
-
-        public boolean isSetGid() {
-            return hasModeFlag(mode, S_ISGID);
-        }
-
-        public boolean isExecutableByCTS() {
-            return executable;
-        }
-    }
-
-    /**
-     * @param path of the file to stat
-     * @param status object to set the fields on
-     * @param statLinks or don't stat links (lstat vs stat)
-     * @return whether or not we were able to stat the file
-     */
-    public native static boolean getFileStatus(String path, FileStatus status, boolean statLinks);
-
-    public native static String getUserName(int uid);
-
-    public native static String getGroupName(int gid);
-
-    /** Display the file's mode like "ls -l" does. */
-    public static String getFormattedPermissions(int mode) {
-        StringBuilder permissions = new StringBuilder("-rwxrwxrwx");
-
-        int[] typeMasks = {S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFDIR, S_IFCHR, S_IFIFO};
-        char[] typeSymbols = {'s', 'l', '-', 'b', 'd', 'c', 'p'};
-        for (int i = 0; i < typeMasks.length; i++) {
-            if (hasModeFlag(mode, typeMasks[i])) {
-                permissions.setCharAt(0, typeSymbols[i]);
-                break;
-            }
-        }
-
-        int[] masks = {S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP,
-                S_IROTH, S_IWOTH, S_IXOTH};
-        for (int i = 0; i < masks.length; i++) {
-            if (!hasModeFlag(mode, masks[i])) {
-                permissions.setCharAt(1 + i, '-');
-            }
-        }
-
-
-        if (hasModeFlag(mode, S_ISUID)) {
-            permissions.setCharAt(3, hasModeFlag(mode, S_IXUSR) ? 's' : 'S');
-        }
-
-        if (hasModeFlag(mode, S_ISGID)) {
-            permissions.setCharAt(6, hasModeFlag(mode, S_IXGRP) ? 's' : 'S');
-        }
-
-        if (hasModeFlag(mode, S_ISVTX)) {
-            permissions.setCharAt(9, hasModeFlag(mode, S_IXOTH) ? 't' : 'T');
-        }
-
-        return permissions.toString();
-    }
-
-    private static boolean hasModeFlag(int mode, int flag) {
-        return (mode & flag) == flag;
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/sixdof/Activities/StartActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/sixdof/Activities/StartActivity.java
index ab8d084..0e01fa1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/sixdof/Activities/StartActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/sixdof/Activities/StartActivity.java
@@ -19,9 +19,12 @@
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.sensors.sixdof.Utils.ReportExporter;
 
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.app.Activity;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
 import android.view.View;
 import android.widget.Button;
 import android.widget.Toast;
@@ -53,6 +56,12 @@
                 startPhase1();
             }
         });
+
+        // If there is no 6DoF sensor advertised, pass trivially.
+        SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
+        if (sensorManager.getDefaultSensor(Sensor.TYPE_POSE_6DOF) == null) {
+            StartActivity.this.setTestResultAndFinish(true);
+        }
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
index 936775e..1ea7608 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
@@ -192,8 +192,7 @@
                     assertEquals(MAX_BUFFER_SIZE, numRead);
                     assertArrayEquals(origBufferMax, bufferMax);
 
-                    // Send two transfers in a row
-                    nextTest(is, os, "measure transfer speed");
+                    nextTest(is, os, "measure out transfer speed");
 
                     byte[] result = new byte[1];
                     long bytesSent = 0;
@@ -209,8 +208,32 @@
                     assertEquals(1, result[0]);
                     // We don't mandate min speed for now, let's collect data on what it is.
                     getReportLog().setSummary(
-                            "Speed", speedKBPS, ResultType.HIGHER_BETTER, ResultUnit.KBPS);
-                    Log.i(LOG_TAG, "Data transfer speed is " + speedKBPS + "KBPS");
+                            "Output USB accesory transfer speed",
+                            speedKBPS,
+                            ResultType.HIGHER_BETTER,
+                            ResultUnit.KBPS);
+                    Log.i(LOG_TAG, "Write data transfer speed is " + speedKBPS + "KBPS");
+
+                    nextTest(is, os, "measure in transfer speed");
+
+                    long bytesRead = 0;
+                    timeStart = SystemClock.elapsedRealtime();
+                    while (bytesRead < TEST_DATA_SIZE_THRESHOLD) {
+                        numRead = is.read(bufferMax);
+                        bytesRead += numRead;
+                    }
+                    numRead = is.read(result);
+                    speedKBPS = (bytesRead * 8 * 1000. / 1024.)
+                            / (SystemClock.elapsedRealtime() - timeStart);
+                    assertEquals(1, numRead);
+                    assertEquals(1, result[0]);
+                    // We don't mandate min speed for now, let's collect data on what it is.
+                    getReportLog().setSummary(
+                            "Input USB accesory transfer speed",
+                            speedKBPS,
+                            ResultType.HIGHER_BETTER,
+                            ResultUnit.KBPS);
+                    Log.i(LOG_TAG, "Read data transfer speed is " + speedKBPS + "KBPS");
 
                     nextTest(is, os, "done");
                 }
diff --git a/apps/CtsVerifierUSBCompanion/src/com/android/cts/verifierusbcompanion/AccessoryTestCompanion.java b/apps/CtsVerifierUSBCompanion/src/com/android/cts/verifierusbcompanion/AccessoryTestCompanion.java
index 5999d3a..5683b32 100644
--- a/apps/CtsVerifierUSBCompanion/src/com/android/cts/verifierusbcompanion/AccessoryTestCompanion.java
+++ b/apps/CtsVerifierUSBCompanion/src/com/android/cts/verifierusbcompanion/AccessoryTestCompanion.java
@@ -164,7 +164,7 @@
                     }
                     break;
 
-                    case "measure transfer speed": {
+                    case "measure out transfer speed": {
                         byte[] buffer = new byte[MAX_BUFFER_SIZE];
 
                         long bytesRead = 0;
@@ -174,9 +174,27 @@
                             bytesRead += numTransferred;
                         }
 
-                        // If the data length is a multple of the maxpacket size read zero packet.
-                        int numTransferred = connection.bulkTransfer(in, buffer, 1, 0);
-                        assertEquals(0, numTransferred);
+                        // If the data length is a multple of the maxpacket size try reading zero
+                        // length packet. On some implementation it might be missing.
+                        connection.bulkTransfer(in, buffer, 1, 100);
+
+                        byte[] confirm = new byte[] {1};
+                        int numTransferred = connection.bulkTransfer(out, confirm, 1, 0);
+                        assertEquals(1, numTransferred);
+                    }
+                    break;
+
+                    case "measure in transfer speed": {
+                        byte[] buffer = new byte[MAX_BUFFER_SIZE];
+
+                        long bytesWritten = 0;
+                        int numTransferred = 0;
+                        while (bytesWritten < TEST_DATA_SIZE_THRESHOLD) {
+                            numTransferred =
+                                    connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 0);
+                            assertEquals(MAX_BUFFER_SIZE, numTransferred);
+                            bytesWritten += numTransferred;
+                        }
 
                         byte[] confirm = new byte[] {1};
                         numTransferred = connection.bulkTransfer(out, confirm, 1, 0);
@@ -192,9 +210,9 @@
                                 0);
                         assertEquals(MAX_BUFFER_SIZE, numTransferred);
 
-                        // If the data length is a multple of the maxpacket size read zero packet.
-                        numTransferred = connection.bulkTransfer(in, empty, 1, 0);
-                        assertEquals(0, numTransferred);
+                        // If the data length is a multple of the maxpacket size try reading zero
+                        // length packet. On some implementation it might be missing.
+                        connection.bulkTransfer(in, empty, 1, 100);
 
                         numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 0);
                         assertEquals(MAX_BUFFER_SIZE, numTransferred);
@@ -214,11 +232,11 @@
                                 MAX_BUFFER_SIZE, 0);
                         assertEquals(MAX_BUFFER_SIZE, numTransferred);
 
-                        // If the data length is a multple of the maxpacket size read zero packet.
-                        numTransferred = connection.bulkTransfer(in, empty, 1, 0);
-                        assertEquals(0, numTransferred);
+                        // If the data length is a multple of the maxpacket size try reading zero
+                        // length packet. On some implementation it might be missing.
+                        connection.bulkTransfer(in, empty, 1, 100);
 
-                        numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 0);
+                        numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 100);
                         assertEquals(MAX_BUFFER_SIZE, numTransferred);
 
                         numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE,
diff --git a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
index 746b840..0fb1d65 100644
--- a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
@@ -17,6 +17,7 @@
 
 import android.app.Notification;
 import android.app.Notification.Action;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
@@ -35,6 +36,7 @@
 
 public class NotificationBot extends BroadcastReceiver {
     private static final String TAG = "NotificationBot";
+    private static final String NOTIFICATION_CHANNEL_ID = TAG;
     private static final String EXTRA_ID = "ID";
     private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
     private static final String ACTION_POST = "com.android.cts.robot.ACTION_POST";
@@ -133,14 +135,19 @@
         final RemoteInput ri = new RemoteInput.Builder("result")
                 .setLabel("Type something here and press send button").build();
 
-        final Notification.Builder nb = new Notification.Builder(context)
+        NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+        notificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
+        final Notification.Builder nb = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                 .setContentTitle(intent.getStringExtra(EXTRA_NOTIFICATION_TITLE))
                 .setSmallIcon(android.R.drawable.ic_popup_sync)
                 .addAction(new Action.Builder(0,
                         "Type something here and press send button", receiverIntent)
                         .addRemoteInput(ri)
                         .build());
-        context.getSystemService(NotificationManager.class).notify(0, nb.build());
+        notificationManager.notify(0, nb.build());
     }
 
     /**
diff --git a/common/device-side/util/Android.mk b/common/device-side/util/Android.mk
index 06e4de5..6a1b7dc 100644
--- a/common/device-side/util/Android.mk
+++ b/common/device-side/util/Android.mk
@@ -23,7 +23,8 @@
     compatibility-common-util-devicesidelib \
     android-support-test \
     ub-uiautomator \
-    mockito-target-minus-junit4
+    mockito-target-minus-junit4 \
+    legacy-android-test
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
index 5b5dc81..0662e81 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
@@ -423,7 +423,8 @@
         int xOnScreen = viewOnScreenXY[0] + view.getWidth() / 2;
         int yOnScreen = viewOnScreenXY[1] + view.getHeight() / 2;
 
-        emulateLongPressOnScreen(instrumentation, xOnScreen, yOnScreen, touchSlop, extraWaitMs);
+        emulateLongPressOnScreen(
+                instrumentation, xOnScreen, yOnScreen, touchSlop, extraWaitMs, true);
     }
 
     /**
@@ -444,7 +445,24 @@
         int xOnScreen = viewOnScreenXY[0] + offsetX;
         int yOnScreen = viewOnScreenXY[1] + offsetY;
 
-        emulateLongPressOnScreen(instrumentation, xOnScreen, yOnScreen, touchSlop, 0);
+        emulateLongPressOnScreen(instrumentation, xOnScreen, yOnScreen, touchSlop, 0, true);
+    }
+
+    /**
+     * Emulates a long press then a linear drag gesture between 2 points across the screen.
+     * This is used for drag selection.
+     *
+     * @param instrumentation the instrumentation used to run the test
+     * @param dragStartX Start X of the emulated drag gesture
+     * @param dragStartY Start Y of the emulated drag gesture
+     * @param dragAmountX X amount of the emulated drag gesture
+     * @param dragAmountY Y amount of the emulated drag gesture
+     */
+    public static void emulateLongPressAndDragGesture(Instrumentation instrumentation,
+            int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) {
+        emulateLongPressOnScreen(instrumentation, dragStartX, dragStartY,
+                0 /* touchSlop */, 0 /* extraWaitMs */, false /* upGesture */);
+        emulateDragGesture(instrumentation, dragStartX, dragStartY, dragAmountX, dragAmountY);
     }
 
     /**
@@ -455,16 +473,19 @@
      * @param yOnScreen Y position on screen for the "long press"
      * @param extraWaitMs extra duration of emulated long press in milliseconds added
      *        after the system-level "long press" timeout.
+     * @param upGesture whether to include an up event.
      */
     private static void emulateLongPressOnScreen(Instrumentation instrumentation,
-            int xOnScreen, int yOnScreen, int touchSlop, long extraWaitMs) {
+            int xOnScreen, int yOnScreen, int touchSlop, long extraWaitMs, boolean upGesture) {
         final UiAutomation uiAutomation = instrumentation.getUiAutomation();
         final long downTime = SystemClock.uptimeMillis();
 
         injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen);
         injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
         SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f) + extraWaitMs);
-        injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen);
+        if (upGesture) {
+            injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen);
+        }
 
         // Wait for the system to process all events in the queue
         instrumentation.waitForIdleSync();
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
index 3711455..1a1ec19 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
@@ -16,6 +16,7 @@
 
 package com.android.compatibility.common.util;
 
+import android.os.Build;
 import android.os.SystemProperties;
 
 /**
@@ -30,17 +31,29 @@
      */
     public static String FIRST_API_LEVEL = "ro.product.first_api_level";
 
-    /** Value to be returned by SystemProperties.getInt() if property is not found */
+    /** Value to be returned by getPropertyInt() if property is not found */
     public static int INT_VALUE_IF_UNSET = -1;
 
     /** Returns whether the device build is the factory ROM */
     public static boolean isFactoryROM() {
         // property should be undefined if and only if the product is factory ROM.
-        return getFirstApiLevel() == INT_VALUE_IF_UNSET;
+        return getPropertyInt(FIRST_API_LEVEL) == INT_VALUE_IF_UNSET;
     }
 
-    /** Return value of read-only property "ro.product.first_api_level" */
+    /**
+     * Return the first API level for this product. If the read-only property is unset,
+     * this means the first API level is the current API level, and the current API level
+     * is returned.
+     */
     public static int getFirstApiLevel() {
-        return SystemProperties.getInt(FIRST_API_LEVEL, INT_VALUE_IF_UNSET);
+        int firstApiLevel = getPropertyInt(FIRST_API_LEVEL);
+        return (firstApiLevel == INT_VALUE_IF_UNSET) ? Build.VERSION.SDK_INT : firstApiLevel;
+    }
+
+    /**
+     * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
+     */
+    public static int getPropertyInt(String property) {
+        return SystemProperties.getInt(property, INT_VALUE_IF_UNSET);
     }
 }
diff --git a/common/host-side/tradefed/res/config/metadata-config.xml b/common/host-side/tradefed/res/config/metadata-config.xml
new file mode 100644
index 0000000..37f1a3e
--- /dev/null
+++ b/common/host-side/tradefed/res/config/metadata-config.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Metadata result reporter for Compatibility suites">
+    <result_reporter class="com.android.compatibility.common.tradefed.result.MetadataReporter" />
+</configuration>
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
index c4a1385..4944da2 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
@@ -21,6 +21,7 @@
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.IShardableListener;
 import com.android.tradefed.util.TimeUtil;
@@ -50,13 +51,15 @@
      * {@inheritDoc}
      */
     @Override
-    public void invocationStarted(IBuildInfo buildInfo) {
-        if (buildInfo == null) {
-            CLog.w("buildInfo should not be null");
+    public void invocationStarted(IInvocationContext context) {
+        if (context == null) {
+            CLog.w("InvocationContext should not be null");
             return;
         }
+        IBuildInfo primaryBuild = context.getBuildInfos().get(0);
+
         // Escape any "%" signs in the device serial.
-        mDeviceSerial = buildInfo.getDeviceSerial().replace("%", "%%");
+        mDeviceSerial = primaryBuild.getDeviceSerial().replace("%", "%%");
     }
 
     /**
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java
new file mode 100644
index 0000000..a3599d8f
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java
@@ -0,0 +1,242 @@
+/*
+ * 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.compatibility.common.tradefed.result;
+
+import com.android.json.stream.JsonWriter;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest.RetryType;
+import com.android.compatibility.common.util.ResultHandler;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.IShardableListener;
+import com.android.tradefed.result.StubTestInvocationListener;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.TimeUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * Write test metadata to the result/metadata folder.
+ */
+public class MetadataReporter extends StubTestInvocationListener implements IShardableListener {
+
+    @Option(name = "include-failure-time", description = "Include timing about tests that failed.")
+    private boolean mIncludeFailures = false;
+
+    @Option(name = "min-test-duration-sec", description = "Ignore test durations less than this.")
+    private int mMinTestDurationSec = 2;
+
+    @Option(name = CompatibilityTest.RETRY_OPTION,
+            shortName = 'r',
+            description = "retry a previous session.",
+            importance = Importance.IF_UNSET)
+    private Integer mRetrySessionId = null;
+
+    private static final String METADATA_DIR = "metadata";
+    private CompatibilityBuildHelper mBuildHelper;
+    private File mMetadataDir;
+    private long mStartTime;
+    private String mCurrentModule;
+    private boolean mTestFailed;
+    private Collection<TestMetadata> mTestMetadata = new LinkedList<>();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IShardableListener clone() {
+        MetadataReporter clone = new MetadataReporter();
+        OptionCopier.copyOptionsNoThrow(this, clone);
+        return clone;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationStarted(IInvocationContext context) {
+        IBuildInfo buildInfo = context.getBuildInfos().get(0);
+        synchronized(this) {
+            if (mBuildHelper == null) {
+                mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+                try {
+                    mMetadataDir = new File(mBuildHelper.getResultDir(), METADATA_DIR);
+                } catch (FileNotFoundException e) {
+                    throw new RuntimeException("Metadata Directory was not created: " +
+                            mMetadataDir.getAbsolutePath());
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunStarted(String id, int numTests) {
+        this.mCurrentModule = id;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testStarted(TestIdentifier test) {
+        mStartTime = System.currentTimeMillis();
+        mTestFailed = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testFailed(TestIdentifier test, String trace) {
+        mTestFailed = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testIgnored(TestIdentifier test) {
+        mTestFailed = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testAssumptionFailure(TestIdentifier test, String trace) {
+        mTestFailed = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+        long duration = (System.currentTimeMillis() - mStartTime) / 1000;
+        if (mTestFailed && !mIncludeFailures) {
+            return;
+        }
+        if (duration < mMinTestDurationSec) {
+            return;
+        }
+
+        TestMetadata metadata = new TestMetadata();
+        metadata.testId = buildTestId(test);
+        metadata.seconds = duration;
+        mTestMetadata.add(metadata);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
+        if (!mTestMetadata.isEmpty()) {
+            tryWriteToFile(mBuildHelper, mCurrentModule, mMetadataDir, mTestMetadata);
+        }
+        mTestMetadata.clear();
+    }
+
+    /** Information about a test's execution. */
+    public static class TestMetadata {
+        // The id of the test
+        String testId;
+        // The duration of the test.
+        long seconds;
+    }
+
+    private static String buildTestId(TestIdentifier test) {
+        return String.format("%s.%s", test.getClassName(), test.getTestName());
+    }
+
+    private static void tryWriteToFile(
+            CompatibilityBuildHelper compatibilityBuildHelper,
+            String moduleName,
+            File metadataDir,
+            Collection<TestMetadata> metadatas) {
+
+        metadataDir.mkdirs();
+
+        String moduleFileName = moduleName + "." + System.currentTimeMillis() + ".json";
+        File metadataFile = new File(metadataDir, moduleFileName);
+        Map<String, String> buildAttributes =
+                compatibilityBuildHelper.getBuildInfo().getBuildAttributes();
+        try (JsonWriter writer = new JsonWriter(new PrintWriter(metadataFile))) {
+            writer.beginObject();
+
+            writer.name("fingerprint");
+            writer.value(buildAttributes.get("cts:build_fingerprint"));
+
+            writer.name("product");
+            writer.value(buildAttributes.get("cts:build_product"));
+
+            writer.name("build_id");
+            writer.value(buildAttributes.get("cts:build_id"));
+
+            writer.name("suite_version");
+            writer.value(compatibilityBuildHelper.getSuiteVersion());
+
+            writer.name("suite_name");
+            writer.value(compatibilityBuildHelper.getSuiteName());
+
+            writer.name("suite_build");
+            writer.value(compatibilityBuildHelper.getSuiteBuild());
+
+            writer.name("module_id");
+            writer.value(moduleName);
+
+            writer.name("test");
+            writer.beginArray();
+            for (TestMetadata metadata : metadatas) {
+                writer.beginObject();
+                writer.name("id");
+                writer.value(metadata.testId);
+                writer.name("sec");
+                writer.value(metadata.seconds);
+                writer.endObject();
+            }
+            writer.endArray();
+
+            writer.endObject();
+        } catch (IOException e) {
+            CLog.e("[%s] While saving metadata.", metadataFile.getAbsolutePath());
+            CLog.e(e);
+        }
+    }
+
+    protected Collection<TestMetadata> getTestMetadata() {
+        return Collections.unmodifiableCollection(mTestMetadata);
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
index f3ae7a3..8140887 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
@@ -17,7 +17,7 @@
 
 import com.android.compatibility.common.tradefed.testtype.IModuleDef;
 import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.InputStreamSource;
@@ -49,9 +49,9 @@
      * {@inheritDoc}
      */
     @Override
-    public void invocationStarted(IBuildInfo buildInfo) {
-        CLog.d("ModuleListener.invocationStarted(%s)", buildInfo.toString());
-        mListener.invocationStarted(buildInfo);
+    public void invocationStarted(IInvocationContext context) {
+        CLog.d("ModuleListener.invocationStarted(%s)", context.getBuildInfos());
+        mListener.invocationStarted(context);
     }
 
     /**
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 dc31b4e..878df6f 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
@@ -16,8 +16,6 @@
 package com.android.compatibility.common.tradefed.result;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.result.InvocationFailureHandler;
-import com.android.compatibility.common.tradefed.result.TestRunHandler;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTest.RetryType;
 import com.android.compatibility.common.util.ICaseResult;
@@ -37,6 +35,7 @@
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ILogSaver;
 import com.android.tradefed.result.ILogSaverListener;
@@ -137,6 +136,8 @@
     // to the master.
     private final ResultReporter mMasterResultReporter;
 
+    private LogFileSaver mTestLogSaver;
+
     /**
      * Default constructor.
      */
@@ -156,34 +157,35 @@
      * {@inheritDoc}
      */
     @Override
-    public void invocationStarted(IBuildInfo buildInfo) {
+    public void invocationStarted(IInvocationContext context) {
+        IBuildInfo primaryBuild = context.getBuildInfos().get(0);
         synchronized(this) {
             if (mBuildHelper == null) {
-                mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+                mBuildHelper = new CompatibilityBuildHelper(primaryBuild);
             }
-            if (mDeviceSerial == null && buildInfo.getDeviceSerial() != null) {
-                mDeviceSerial = buildInfo.getDeviceSerial();
+            if (mDeviceSerial == null && primaryBuild.getDeviceSerial() != null) {
+                mDeviceSerial = primaryBuild.getDeviceSerial();
             }
             mCanMarkDone = canMarkDone(mBuildHelper.getRecentCommandLineArgs());
         }
 
         if (isShardResultReporter()) {
             // Shard ResultReporters forward invocationStarted to the mMasterResultReporter
-            mMasterResultReporter.invocationStarted(buildInfo);
+            mMasterResultReporter.invocationStarted(context);
             return;
         }
 
         // NOTE: Everything after this line only applies to the master ResultReporter.
 
         synchronized(this) {
-            if (buildInfo.getDeviceSerial() != null) {
+            if (primaryBuild.getDeviceSerial() != null) {
                 // The master ResultReporter collects all device serials being used
                 // for the current implementation.
-                mMasterDeviceSerials.add(buildInfo.getDeviceSerial());
+                mMasterDeviceSerials.add(primaryBuild.getDeviceSerial());
             }
 
             // The master ResultReporter collects all buildInfos.
-            mMasterBuildInfos.add(buildInfo);
+            mMasterBuildInfos.add(primaryBuild);
 
             if (mResultDir == null) {
                 // For the non-sharding case, invocationStarted is only called once,
@@ -232,7 +234,7 @@
             mLogDir = new File(mBuildHelper.getLogsDir(),
                     CompatibilityBuildHelper.getDirSuffix(mBuildHelper.getStartTime()));
         } catch (FileNotFoundException e) {
-            e.printStackTrace();
+            CLog.e(e);
         }
         if (mLogDir != null && mLogDir.mkdirs()) {
             debug("Created log dir %s", mLogDir.getAbsolutePath());
@@ -241,6 +243,9 @@
             throw new IllegalArgumentException(String.format("Could not create log dir %s",
                     mLogDir.getAbsolutePath()));
         }
+        if (mTestLogSaver == null) {
+            mTestLogSaver = new LogFileSaver(mLogDir);
+        }
     }
 
     /**
@@ -490,6 +495,11 @@
                     mBuildHelper.getSuiteBuild(), mResult, mResultDir, startTime,
                     elapsedTime + startTime, mReferenceUrl, getLogUrl(),
                     mBuildHelper.getCommandLineArgs());
+            if (mRetrySessionId != null) {
+                copyRetryFiles(ResultHandler.getResultDirectory(
+                        mBuildHelper.getResultsDir(), mRetrySessionId), mResultDir);
+            }
+            File zippedResults = zipResults(mResultDir);
             // Create failure report after zip file so extra data is not uploaded
             File failureReport = ResultHandler.createFailureReport(resultFile);
             if (failureReport.exists()) {
@@ -497,7 +507,6 @@
             } else {
                 info("Test Result: %s", resultFile.getCanonicalPath());
             }
-            File zippedResults = zipResults(mResultDir);
             debug("Full Result: %s", zippedResults.getCanonicalPath());
 
             saveLog(resultFile, zippedResults);
@@ -538,12 +547,11 @@
             return;
         }
         try {
-            LogFileSaver saver = new LogFileSaver(mLogDir);
-            File logFile = saver.saveAndZipLogData(name, type, stream.createInputStream());
+            File logFile = mTestLogSaver.saveAndZipLogData(name, type, stream.createInputStream());
             debug("Saved logs for %s in %s", name, logFile.getAbsolutePath());
         } catch (IOException e) {
             warn("Failed to write log for %s", name);
-            e.printStackTrace();
+            CLog.e(e);
         }
     }
 
@@ -714,6 +722,33 @@
     }
 
     /**
+     * Recursively copy any other files found in the previous session's result directory to the
+     * new result directory, so long as they don't already exist. For example, a "screenshots"
+     * directory generated in a previous session by a passing test will not be generated on retry
+     * unless copied from the old result directory.
+     *
+     * @param oldResultsDir
+     * @param newResultsDir
+     */
+    static void copyRetryFiles(File oldResultsDir, File newResultsDir) {
+        File[] oldFiles = oldResultsDir.listFiles();
+        for (File oldFile : oldFiles) {
+            File newFile = new File (newResultsDir, oldFile.getName());
+            if (!newFile.exists()) {
+                try {
+                    if (oldFile.isDirectory()) {
+                        FileUtil.recursiveCopy(oldFile, newFile);
+                    } else {
+                        FileUtil.copyFile(oldFile, newFile);
+                    }
+                } catch (IOException e) {
+                    warn("Failed to copy file \"%s\" from previous session", oldFile.getName());
+                }
+            }
+        }
+    }
+
+    /**
      * Zip the contents of the given results directory.
      *
      * @param resultsDir
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index a10c3b5..7256437 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -41,7 +41,6 @@
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.ResultForwarder;
 import com.android.tradefed.suite.checker.ISystemStatusChecker;
 import com.android.tradefed.suite.checker.ISystemStatusCheckerReceiver;
 import com.android.tradefed.testtype.Abi;
@@ -70,7 +69,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
@@ -457,17 +455,11 @@
                 if (checkers != null && !checkers.isEmpty()) {
                     runPreModuleCheck(module.getName(), checkers, mDevice, listener);
                 }
-                // Workaround to b/34202787: Add result forwarder that ensures module is reported
-                // with 0 tests if test runner doesn't report anything in this case.
-                // Necessary for solution to b/33289177, in which completed modules may sometimes
-                // not be marked done until retried with 0 tests.
-                ModuleResultForwarder moduleListener = new ModuleResultForwarder(listener);
                 try {
                     if (module.getTest() instanceof IBuildReceiver) {
                         ((IBuildReceiver)module.getTest()).setBuild(mBuildHelper.getBuildInfo());
                     }
-                    module.run(moduleListener);
-                    moduleListener.finish(module.getId());
+                    module.run(listener);
                 } catch (DeviceUnresponsiveException due) {
                     // being able to catch a DeviceUnresponsiveException here implies that recovery
                     // was successful, and test execution should proceed to next module
@@ -810,32 +802,4 @@
     public void setCollectTestsOnly(boolean collectTestsOnly) {
         mCollectTestsOnly = collectTestsOnly;
     }
-
-    private class ModuleResultForwarder extends ResultForwarder {
-
-        private boolean mTestRunStarted = false;
-        private ITestInvocationListener mListener;
-
-        public ModuleResultForwarder(ITestInvocationListener listener) {
-            super(listener);
-            mListener = listener;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void testRunStarted(String name, int numTests) {
-            mListener.testRunStarted(name, numTests);
-            mTestRunStarted = true;
-        }
-
-        public void finish(String moduleId) {
-            if (!mTestRunStarted) {
-                mListener.testRunStarted(moduleId, 0);
-                mListener.testRunEnded(0, Collections.emptyMap());
-            }
-        }
-    }
-
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
index be4308c..d4f73aab 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
@@ -19,7 +19,6 @@
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
-import com.android.tradefed.config.OptionCopier;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
@@ -74,13 +73,9 @@
      */
     @Override
     protected HostTest createHostTest(Class<?> classObj) {
-        JarHostTest test = new JarHostTest();
-        OptionCopier.copyOptionsNoThrow(this, test);
-        if (classObj != null) {
-            test.setClassName(classObj.getName());
-        } else {
-            test.mJars.clear();
-        }
+        JarHostTest test = (JarHostTest) super.createHostTest(classObj);
+        // clean the jar option since we are loading directly from classes after.
+        test.mJars = new HashSet<>();
         return test;
     }
 
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
index 8566d5a..29009f1 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -27,6 +27,7 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.ResultForwarder;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.ITargetCleaner;
 import com.android.tradefed.targetprep.ITargetPreparer;
@@ -218,8 +219,6 @@
      */
     @Override
     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
-        IModuleListener moduleListener = new ModuleListener(this, listener);
-
         CLog.d("Running module %s", toString());
         // Run DynamicConfigPusher setup once more, in case cleaner has previously
         // removed dynamic config file from the target (see b/32877809)
@@ -242,7 +241,11 @@
             ((IDeviceTest) mTest).setDevice(mDevice);
         }
 
-        mTest.run(moduleListener);
+        IModuleListener moduleListener = new ModuleListener(this, listener);
+        // Guarantee events testRunStarted and testRunEnded in case underlying test runner does not
+        ModuleFinisher moduleFinisher = new ModuleFinisher(moduleListener);
+        mTest.run(moduleFinisher);
+        moduleFinisher.finish();
 
         // Tear down
         for (ITargetCleaner cleaner : mCleaners) {
@@ -318,4 +321,37 @@
     public void setCollectTestsOnly(boolean collectTestsOnly) {
         ((ITestCollector) mTest).setCollectTestsOnly(collectTestsOnly);
     }
+
+    /*
+     * ResultForwarder that tracks whether method testRunStarted() has been called for its
+     * listener. If not, invoking finish() will call testRunStarted with 0 tests for this module,
+     * as well as testRunEnded with 0 ms elapsed.
+     */
+    private class ModuleFinisher extends ResultForwarder {
+
+        private boolean mFinished;
+        private ITestInvocationListener mListener;
+
+        public ModuleFinisher(ITestInvocationListener listener) {
+            super(listener);
+            mListener = listener;
+            mFinished = false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testRunStarted(String name, int numTests) {
+            mListener.testRunStarted(name, numTests);
+            mFinished = true;
+        }
+
+        public void finish() {
+            if (!mFinished) {
+                mListener.testRunStarted(mId, 0);
+                mListener.testRunEnded(0, Collections.emptyMap());
+            }
+        }
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
index 09d1219..3402757 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
@@ -15,9 +15,9 @@
  */
 package com.android.compatibility.common.tradefed.testtype;
 
-import com.android.compatibility.common.tradefed.util.LinearPartition;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.result.TestRunHandler;
+import com.android.compatibility.common.tradefed.util.LinearPartition;
 import com.android.compatibility.common.util.TestFilter;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.ConfigurationException;
diff --git a/common/host-side/tradefed/tests/res/testtype/testJar2.jar b/common/host-side/tradefed/tests/res/testtype/testJar2.jar
new file mode 100644
index 0000000..4433986
--- /dev/null
+++ b/common/host-side/tradefed/tests/res/testtype/testJar2.jar
Binary files differ
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index 4484d49..57e2b9b 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -21,6 +21,7 @@
 import com.android.compatibility.common.tradefed.presubmit.PresubmitSetupValidation;
 import com.android.compatibility.common.tradefed.result.ChecksumReporterTest;
 import com.android.compatibility.common.tradefed.result.ConsoleReporterTest;
+import com.android.compatibility.common.tradefed.result.MetadataReporterTest;
 import com.android.compatibility.common.tradefed.result.ResultReporterTest;
 import com.android.compatibility.common.tradefed.result.SubPlanCreatorTest;
 import com.android.compatibility.common.tradefed.targetprep.PropertyCheckTest;
@@ -65,6 +66,12 @@
         addTestSuite(SubPlanCreatorTest.class);
 
         // targetprep
+        addTestSuite(CompatibilityTestTest.class);
+        addTestSuite(OptionHelperTest.class);
+        addTestSuite(CollectorUtilTest.class);
+        addTestSuite(MetadataReporterTest.class);
+        addTestSuite(ModuleDefTest.class);
+        addTestSuite(ModuleRepoTest.class);
         addTestSuite(PropertyCheckTest.class);
         addTestSuite(SettingsPreparerTest.class);
 
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java
index d5062b3..763acb7 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java
@@ -26,6 +26,8 @@
 import com.android.compatibility.common.util.TestStatus;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
 import com.android.tradefed.util.FileUtil;
 
 import junit.framework.TestCase;
@@ -34,6 +36,9 @@
 import java.io.FileWriter;
 import java.io.IOException;
 
+/**
+ * Unit tests for {@link ChecksumReporter}
+ */
 public class ChecksumReporterTest extends TestCase {
 
     private static final String ROOT_PROPERTY = "TESTS_ROOT";
@@ -81,8 +86,10 @@
         setter.setOptionValue("plan", SUITE_PLAN);
         setter.setOptionValue("dynamic-config-url", "");
         mBuildInfo = provider.getBuild();
+        IInvocationContext context = new InvocationContext();
+        context.addDeviceBuildInfo("fakeDevice", mBuildInfo);
 
-        resultReporter.invocationStarted(mBuildInfo);
+        resultReporter.invocationStarted(context);
         mInvocationResult = resultReporter.getResult();
         mModuleResult = mInvocationResult.getOrCreateModule("Module-1");
         mModuleResult.setDone(true);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
index 272b94d..9102f1a 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
@@ -17,8 +17,8 @@
 package com.android.compatibility.common.tradefed.result;
 
 import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.util.AbiUtils;
 
 import junit.framework.TestCase;
@@ -43,7 +43,7 @@
             "at four.big.insects.Marley.sing(Marley.java:10)";
 
     private ConsoleReporter mReporter;
-    private IBuildInfo mBuildInfo;
+    private IInvocationContext mContext;
 
     @Override
     public void setUp() throws Exception {
@@ -58,7 +58,7 @@
     }
 
     public void testResultReporting_singleModule() throws Exception {
-        mReporter.invocationStarted(mBuildInfo);
+        mReporter.invocationStarted(mContext);
         mReporter.testRunStarted(ID, 3);
         runTests();
 
@@ -73,7 +73,7 @@
     }
 
     public void testResultReporting_multipleModules() throws Exception {
-        mReporter.invocationStarted(mBuildInfo);
+        mReporter.invocationStarted(mContext);
         mReporter.testRunStarted(ID, 3);
         runTests();
 
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java
new file mode 100644
index 0000000..17f27a5
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.compatibility.common.tradefed.result;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.ICaseResult;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.ITestResult;
+import com.android.compatibility.common.util.TestStatus;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+public class MetadataReporterTest extends TestCase {
+
+    private static final String MIN_TEST_DURATION = "2";
+    private static final String ROOT_PROPERTY = "TESTS_ROOT";
+    private static final String BUILD_NUMBER = "2";
+    private static final String SUITE_PLAN = "cts";
+    private static final String DYNAMIC_CONFIG_URL = "";
+    private static final String ROOT_DIR_NAME = "root";
+    private static final String BASE_DIR_NAME = "android-tests";
+    private static final String TESTCASES = "testcases";
+    private static final String NAME = "ModuleName";
+    private static final String ABI = "mips64";
+    private static final String ID = AbiUtils.createId(ABI, NAME);
+    private static final String CLASS = "android.test.FoorBar";
+    private static final String METHOD_1 = "testBlah1";
+    private static final String METHOD_2 = "testBlah2";
+    private static final String METHOD_3 = "testBlah3";
+    private static final String TEST_1 = String.format("%s#%s", CLASS, METHOD_1);
+    private static final String TEST_2 = String.format("%s#%s", CLASS, METHOD_2);
+    private static final String TEST_3 = String.format("%s#%s", CLASS, METHOD_3);
+    private static final String STACK_TRACE = "Something small is not alright\n " +
+            "at four.big.insects.Marley.sing(Marley.java:10)";
+    private static final String RESULT_DIR = "result123";
+    private static final String[] FORMATTING_FILES = {
+        "compatibility_result.css",
+        "compatibility_result.xsd",
+        "compatibility_result.xsl",
+        "logo.png"};
+    private static final long START_TIME = 123456L;
+
+    private MetadataReporter mReporter;
+    private IBuildInfo mBuildInfo;
+    private IInvocationContext mContext;
+    private CompatibilityBuildHelper mBuildHelper;
+
+    private File mRoot = null;
+    private File mBase = null;
+    private File mTests = null;
+
+    @Override
+    public void setUp() throws Exception {
+
+        mReporter = new MetadataReporter();
+        OptionSetter setter = new OptionSetter(mReporter);
+        setter.setOptionValue("min-test-duration-sec", MIN_TEST_DURATION);
+        mRoot = FileUtil.createTempDir(ROOT_DIR_NAME);
+        mBase = new File(mRoot, BASE_DIR_NAME);
+        mBase.mkdirs();
+        mTests = new File(mBase, TESTCASES);
+        mTests.mkdirs();
+        System.setProperty(ROOT_PROPERTY, mRoot.getAbsolutePath());
+        mBuildInfo = new BuildInfo(BUILD_NUMBER, "", "");
+        mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        mContext = new InvocationContext();
+        mContext.addDeviceBuildInfo("fakeDevice", mBuildInfo);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mReporter = null;
+        FileUtil.recursiveDelete(mRoot);
+    }
+
+    public void testResultReportingFastTests() throws Exception {
+        mReporter.invocationStarted(mContext);
+        mReporter.testRunStarted(ID, 3);
+        runFastTests();
+
+        Collection<MetadataReporter.TestMetadata> metadata = mReporter.getTestMetadata();
+        assertTrue(metadata.isEmpty());
+
+        mReporter.testRunEnded(10, new HashMap<String, String>());
+        mReporter.invocationEnded(10);
+    }
+
+    public void testResultReportingSlowTests() throws Exception {
+        mReporter.invocationStarted(mContext);
+        mReporter.testRunStarted(ID, 3);
+        runSlowTests();
+
+        Collection<MetadataReporter.TestMetadata> metadata = mReporter.getTestMetadata();
+        assertEquals(metadata.size(), 2); // Two passing slow tests.
+
+        mReporter.testRunEnded(10, new HashMap<String, String>());
+        mReporter.invocationEnded(10);
+    }
+
+
+    /** Run 4 test. */
+    private void runFastTests() {
+        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        mReporter.testStarted(test1);
+        mReporter.testEnded(test1, new HashMap<String, String>());
+
+        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        mReporter.testStarted(test2);
+        mReporter.testEnded(test1, new HashMap<String, String>());
+
+        TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+        mReporter.testStarted(test3);
+        mReporter.testFailed(test3, STACK_TRACE);
+
+        TestIdentifier test4 = new TestIdentifier(CLASS, METHOD_3);
+        mReporter.testStarted(test4);
+        mReporter.testIgnored(test4);
+    }
+
+    /** Run 4 tests with delays. 2 passing, 1 failed, 1 ignored. */
+    private void runSlowTests() throws InterruptedException {
+        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        mReporter.testStarted(test1);
+        Thread.sleep(3000);
+        mReporter.testEnded(test1, new HashMap<String, String>());
+
+        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        mReporter.testStarted(test2);
+        Thread.sleep(3000);
+        mReporter.testEnded(test1, new HashMap<String, String>());
+
+        TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+        mReporter.testStarted(test3);
+        Thread.sleep(3000);
+        mReporter.testFailed(test3, STACK_TRACE);
+
+        TestIdentifier test4 = new TestIdentifier(CLASS, METHOD_3);
+        mReporter.testStarted(test4);
+        Thread.sleep(3000);
+        mReporter.testIgnored(test4);
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
index 5af79dc..6fd2401 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
@@ -26,6 +26,11 @@
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.result.ByteArrayInputStreamSource;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.FileUtil;
 
@@ -36,6 +41,9 @@
 import java.util.HashMap;
 import java.util.List;
 
+/**
+ * Unit tests for {@link ResultReporter}
+ */
 public class ResultReporterTest extends TestCase {
 
     private static final String ROOT_PROPERTY = "TESTS_ROOT";
@@ -67,6 +75,7 @@
 
     private ResultReporter mReporter;
     private IBuildInfo mBuildInfo;
+    private IInvocationContext mContext;
     private CompatibilityBuildHelper mBuildHelper;
 
     private File mRoot = null;
@@ -101,6 +110,8 @@
         setter.setOptionValue("dynamic-config-url", DYNAMIC_CONFIG_URL);
         mBuildInfo = provider.getBuild();
         mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        mContext = new InvocationContext();
+        mContext.addDeviceBuildInfo("fakeDevice", mBuildInfo);
     }
 
     @Override
@@ -110,7 +121,7 @@
     }
 
     public void testSetup() throws Exception {
-        mReporter.invocationStarted(mBuildInfo);
+        mReporter.invocationStarted(mContext);
         // Should have created a directory for the logs
         File[] children = mBuildHelper.getLogsDir().listFiles();
         assertTrue("Didn't create logs dir", children.length == 1 && children[0].isDirectory());
@@ -130,7 +141,7 @@
     }
 
     public void testResultReporting() throws Exception {
-        mReporter.invocationStarted(mBuildInfo);
+        mReporter.invocationStarted(mContext);
         mReporter.testRunStarted(ID, 2);
         TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
         mReporter.testStarted(test1);
@@ -141,6 +152,7 @@
         TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
         mReporter.testStarted(test3);
         mReporter.testFailed(test3, STACK_TRACE);
+        mReporter.testEnded(test3, new HashMap<String, String>());
         mReporter.testRunEnded(10, new HashMap<String, String>());
         mReporter.invocationEnded(10);
         IInvocationResult result = mReporter.getResult();
@@ -188,7 +200,7 @@
     public void testRepeatedExecutions() throws Exception {
         String[] methods = new String[] {METHOD_1, METHOD_2, METHOD_3};
 
-        mReporter.invocationStarted(mBuildInfo);
+        mReporter.invocationStarted(mContext);
 
         makeTestRun(methods, new boolean[] {true, false, true});
         makeTestRun(methods, new boolean[] {true, false, false});
@@ -234,7 +246,7 @@
     }
 
     public void testRetry() throws Exception {
-        mReporter.invocationStarted(mBuildInfo);
+        mReporter.invocationStarted(mContext);
 
         // Set up IInvocationResult with existing results from previous session
         mReporter.testRunStarted(ID, 2);
@@ -289,7 +301,7 @@
     }
 
     public void testRetryCanSetDone() throws Exception {
-        mReporter.invocationStarted(mBuildInfo);
+        mReporter.invocationStarted(mContext);
         // Set mCanMarkDone directly (otherwise we must build result directory, write XML, and
         // perform actual retry)
         mReporter.mCanMarkDone = true;
@@ -328,7 +340,7 @@
     }
 
     public void testRetryCannotSetDone() throws Exception {
-        mReporter.invocationStarted(mBuildInfo);
+        mReporter.invocationStarted(mContext);
         // Set mCanMarkDone directly (otherwise we must build result directory, write XML, and
         // perform actual retry)
         mReporter.mCanMarkDone = false;
@@ -367,7 +379,7 @@
     }
 
     public void testResultReporting_moduleNotDone() throws Exception {
-        mReporter.invocationStarted(mBuildInfo);
+        mReporter.invocationStarted(mContext);
         mReporter.testRunStarted(ID, 2);
         TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
         mReporter.testStarted(test1);
@@ -406,4 +418,27 @@
                     file.exists() && file.isFile() && file.length() > 0);
         }
     }
+
+    /**
+     * Ensure that when {@link ResultReporter#testLog(String, LogDataType, InputStreamSource)} is
+     * called, a single invocation result folder is created and populated.
+     */
+    public void testTestLog() throws Exception {
+        InputStreamSource fakeData = new ByteArrayInputStreamSource("test".getBytes());
+        mReporter.invocationStarted(mContext);
+        mReporter.testLog("test1", LogDataType.LOGCAT, fakeData);
+        // date folder
+        assertEquals(1, mBuildHelper.getLogsDir().list().length);
+        // inv_ folder
+        assertEquals(1, mBuildHelper.getLogsDir().listFiles()[0].list().length);
+        // actual logs
+        assertEquals(1, mBuildHelper.getLogsDir().listFiles()[0].listFiles()[0].list().length);
+        mReporter.testLog("test2", LogDataType.LOGCAT, fakeData);
+        // date folder
+        assertEquals(1, mBuildHelper.getLogsDir().list().length);
+        // inv_ folder
+        assertEquals(1, mBuildHelper.getLogsDir().listFiles()[0].list().length);
+        // actual logs
+        assertEquals(2, mBuildHelper.getLogsDir().listFiles()[0].listFiles()[0].list().length);
+    }
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
index 9c28197..b3bfb91 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
@@ -16,9 +16,11 @@
 package com.android.compatibility.common.tradefed.testtype;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.HostTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.util.FileUtil;
@@ -33,6 +35,10 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -41,10 +47,34 @@
 public class JarHostTestTest extends TestCase {
 
     private static final String TEST_JAR1 = "/testtype/testJar1.jar";
+    private static final String TEST_JAR2 = "/testtype/testJar2.jar";
     private JarHostTest mTest;
     private File mTestDir = null;
 
     /**
+     * More testable version of {@link JarHostTest}
+     */
+    public static class JarHostTestable extends JarHostTest {
+
+        public static File mTestDir;
+        public JarHostTestable() {}
+
+        public JarHostTestable(File testDir) {
+            mTestDir = testDir;
+        }
+
+        @Override
+        CompatibilityBuildHelper createBuildHelper(IBuildInfo info) {
+            return new CompatibilityBuildHelper(info) {
+                @Override
+                public File getTestsDir() throws FileNotFoundException {
+                    return mTestDir;
+                }
+            };
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -117,17 +147,7 @@
      */
     public void testSplit_withJar() throws Exception {
         File testJar = getJarResource(TEST_JAR1, mTestDir);
-        mTest = new JarHostTest() {
-            @Override
-            CompatibilityBuildHelper createBuildHelper(IBuildInfo info) {
-                return new CompatibilityBuildHelper(info) {
-                    @Override
-                    public File getTestsDir() throws FileNotFoundException {
-                        return mTestDir;
-                    }
-                };
-            }
-        };
+        mTest = new JarHostTestable(mTestDir);
         OptionSetter setter = new OptionSetter(mTest);
         setter.setOptionValue("jar", testJar.getName());
         List<IRemoteTest> res = (List<IRemoteTest>)mTest.split();
@@ -144,31 +164,97 @@
      * Test that {@link JarHostTest#getTestShard(int, int)} can split classes coming from a jar.
      */
     public void testGetTestShard_withJar() throws Exception {
-        File testJar = getJarResource(TEST_JAR1, mTestDir);
-        mTest = new JarHostTest() {
-            @Override
-            CompatibilityBuildHelper createBuildHelper(IBuildInfo info) {
-                return new CompatibilityBuildHelper(info) {
-                    @Override
-                    public File getTestsDir() throws FileNotFoundException {
-                        return mTestDir;
-                    }
-                };
-            }
-        };
+        File testJar = getJarResource(TEST_JAR2, mTestDir);
+        mTest = new JarHostTestLoader(mTestDir, testJar);
+        mTest.setBuild(new BuildInfo());
+        ITestDevice device = EasyMock.createNiceMock(ITestDevice.class);
+        mTest.setDevice(device);
         OptionSetter setter = new OptionSetter(mTest);
         setter.setOptionValue("jar", testJar.getName());
-        IRemoteTest shard1 = mTest.getTestShard(3, 0);
+        // full class count without sharding
+        assertEquals(238, mTest.countTestCases());
+
+        // only one shard
+        IRemoteTest oneShard = mTest.getTestShard(1, 0);
+        assertTrue(oneShard instanceof JarHostTest);
+        ((JarHostTest)oneShard).setBuild(new BuildInfo());
+        ((JarHostTest)oneShard).setDevice(device);
+        assertEquals(238, ((JarHostTest)oneShard).countTestCases());
+
+        // 5 shards total the number of tests.
+        int total = 0;
+        IRemoteTest shard1 = mTest.getTestShard(5, 0);
         assertTrue(shard1 instanceof JarHostTest);
-        assertEquals("[android.ui.cts.TaskSwitchingTest]",
-                ((JarHostTest)shard1).getClassNames().toString());
-        IRemoteTest shard2 = mTest.getTestShard(3, 1);
+        ((JarHostTest)shard1).setBuild(new BuildInfo());
+        ((JarHostTest)shard1).setDevice(device);
+        assertEquals(78, ((JarHostTest)shard1).countTestCases());
+        total += ((JarHostTest)shard1).countTestCases();
+
+        IRemoteTest shard2 = mTest.getTestShard(5, 1);
         assertTrue(shard2 instanceof JarHostTest);
-        assertEquals("[android.ui.cts.InstallTimeTest]",
-                ((JarHostTest)shard2).getClassNames().toString());
-        // Not enough class for a real 3rd shard, so it's an empty placeholder instead.
-        IRemoteTest shard3 = mTest.getTestShard(3, 2);
+        ((JarHostTest)shard2).setBuild(new BuildInfo());
+        ((JarHostTest)shard2).setDevice(device);
+        assertEquals(63, ((JarHostTest)shard2).countTestCases());
+        total += ((JarHostTest)shard2).countTestCases();
+
+        IRemoteTest shard3 = mTest.getTestShard(5, 2);
         assertTrue(shard3 instanceof JarHostTest);
-        assertTrue(((JarHostTest)shard3).getClassNames().isEmpty());
+        ((JarHostTest)shard3).setBuild(new BuildInfo());
+        ((JarHostTest)shard3).setDevice(device);
+        assertEquals(42, ((JarHostTest)shard3).countTestCases());
+        total += ((JarHostTest)shard3).countTestCases();
+
+        IRemoteTest shard4 = mTest.getTestShard(5, 3);
+        assertTrue(shard4 instanceof JarHostTest);
+        ((JarHostTest)shard4).setBuild(new BuildInfo());
+        ((JarHostTest)shard4).setDevice(device);
+        assertEquals(14, ((JarHostTest)shard4).countTestCases());
+        total += ((JarHostTest)shard4).countTestCases();
+
+        IRemoteTest shard5 = mTest.getTestShard(5, 4);
+        assertTrue(shard5 instanceof JarHostTest);
+        ((JarHostTest)shard5).setBuild(new BuildInfo());
+        ((JarHostTest)shard5).setDevice(device);
+        assertEquals(41, ((JarHostTest)shard5).countTestCases());
+        total += ((JarHostTest)shard5).countTestCases();
+
+        assertEquals(238, total);
+    }
+
+    /**
+     * Testable version of {@link JarHostTest} that allows adding jar to classpath for testing
+     * purpose.
+     */
+    public static class JarHostTestLoader extends JarHostTestable {
+
+        private static File mTestJar;
+
+        public JarHostTestLoader() {}
+
+        public JarHostTestLoader(File testDir, File jar) {
+            super(testDir);
+            mTestJar = jar;
+        }
+
+        @Override
+        CompatibilityBuildHelper createBuildHelper(IBuildInfo info) {
+            return new CompatibilityBuildHelper(info) {
+                @Override
+                public File getTestsDir() throws FileNotFoundException {
+                    return mTestDir;
+                }
+            };
+        }
+        @Override
+        protected ClassLoader getClassLoader() {
+            ClassLoader child = super.getClassLoader();
+            try {
+                child = new URLClassLoader(Arrays.asList(mTestJar.toURI().toURL())
+                        .toArray(new URL[]{}), super.getClassLoader());
+            } catch (MalformedURLException e) {
+                CLog.e(e);
+            }
+            return child;
+        }
     }
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
index 02eae24..4aa67ac 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
@@ -28,9 +28,12 @@
 import com.android.tradefed.testtype.ITestFilterReceiver;
 import com.android.tradefed.util.AbiUtils;
 
+import org.easymock.EasyMock;
+
 import junit.framework.TestCase;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -49,6 +52,21 @@
         assertEquals("Incorrect Name", NAME, def.getName());
     }
 
+    public void testModuleFinisher() throws Exception {
+        IAbi abi = new Abi(ABI, "");
+        MockRemoteTest mockTest = new MockRemoteTest();
+        IModuleDef def = new ModuleDef(NAME, abi, mockTest, new ArrayList<ITargetPreparer>());
+        ITestInvocationListener mockListener = EasyMock.createMock(ITestInvocationListener.class);
+        // listener should receive testRunStarted/testRunEnded events even for no-op run() method
+        mockListener.testRunStarted(ID, 0);
+        EasyMock.expectLastCall().once();
+        mockListener.testRunEnded(0, Collections.emptyMap());
+        EasyMock.expectLastCall().once();
+        EasyMock.replay(mockListener);
+        def.run(mockListener);
+        EasyMock.verify(mockListener);
+    }
+
     private class MockRemoteTest implements IRemoteTest, ITestFilterReceiver, IAbiReceiver,
             IRuntimeHintProvider, ITestCollector {
 
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java b/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
new file mode 100644
index 0000000..199b826
--- /dev/null
+++ b/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
@@ -0,0 +1,48 @@
+/*
+ * 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.compatibility.common.util;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Host-side utility class for reading properties and gathering information for testing
+ * Android device compatibility.
+ */
+public class PropertyUtil {
+
+    /**
+     * Name of read-only property detailing the first API level for which the product was
+     * shipped. Property should be undefined for factory ROM products.
+     */
+    public static String FIRST_API_LEVEL = "ro.product.first_api_level";
+
+    /** Returns whether the device build is the factory ROM */
+    public static boolean isFactoryROM(ITestDevice device) throws DeviceNotAvailableException {
+        // first API level property should be undefined if and only if the product is factory ROM.
+        return device.getProperty(FIRST_API_LEVEL) == null;
+    }
+
+    /**
+     * Return the first API level for this product. If the read-only property is unset,
+     * this means the first API level is the current API level, and the current API level
+     * is returned.
+     */
+    public static int getFirstApiLevel(ITestDevice device) throws DeviceNotAvailableException {
+        String propString = device.getProperty(FIRST_API_LEVEL);
+        return (propString == null) ? device.getApiLevel() : Integer.parseInt(propString);
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index 2c63ae4..c251cbe 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -509,6 +509,22 @@
     }
 
     /**
+     * Get the result directory for the given sessionId.
+     */
+    public static File getResultDirectory(File resultsDir, Integer sessionId) {
+        if (sessionId < 0) {
+            throw new IllegalArgumentException(
+                String.format("Invalid session id [%d] ", sessionId));
+        }
+        List<File> allResultDirs = getResultDirectories(resultsDir);
+        if (sessionId >= allResultDirs.size()) {
+            throw new IllegalArgumentException(String.format("Invalid session id [%d], results" +
+                    "directory contains only %d results", sessionId, allResultDirs.size()));
+        }
+        return allResultDirs.get(sessionId);
+    }
+
+    /**
      * Get a list of child directories that contain test invocation results
      * @param resultsDir the root test result directory
      * @return
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
new file mode 100644
index 0000000..034da99
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.appsecurity.cts;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+/**
+ * Tests for the instant cookie APIs
+ */
+public class InstantCookieHostTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String INSTANT_COOKIE_APP_APK = "CtsInstantCookieApp.apk";
+    private static final String INSTANT_COOKIE_APP_PKG = "test.instant.cookie";
+
+    private CompatibilityBuildHelper mBuildHelper;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        uninstallPackage(INSTANT_COOKIE_APP_PKG);
+        clearUserData(INSTANT_COOKIE_APP_PKG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(INSTANT_COOKIE_APP_PKG);
+    }
+
+    public void testCookieUpdateAndRetrieval() throws Exception {
+        assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
+        runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+                "testCookieUpdateAndRetrieval");
+    }
+
+    public void testCookiePersistedAcrossInstantInstalls() throws Exception {
+        assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
+        runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+                "testCookiePersistedAcrossInstantInstalls1");
+        uninstallPackage(INSTANT_COOKIE_APP_PKG);
+        assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
+        runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+                "testCookiePersistedAcrossInstantInstalls2");
+    }
+
+    public void testCookiePersistedUpgradeFromInstant() throws Exception {
+        assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
+        runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+                "testCookiePersistedUpgradeFromInstant1");
+        assertNull(installPackage(INSTANT_COOKIE_APP_APK, true, false));
+        runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+                "testCookiePersistedUpgradeFromInstant2");
+    }
+
+    public void testCookieResetOnNonInstantReinstall() throws Exception {
+        assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, false));
+        runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+                "testCookieResetOnNonInstantReinstall1");
+        uninstallPackage(INSTANT_COOKIE_APP_PKG);
+        assertNull(installPackage(INSTANT_COOKIE_APP_APK, true, false));
+        runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+                "testCookieResetOnNonInstantReinstall2");
+    }
+
+    private String clearUserData(String packageName) throws DeviceNotAvailableException {
+        return getDevice().executeShellCommand("pm clear " + packageName);
+    }
+
+    private String installPackage(String apk, boolean replace, boolean instant) throws Exception {
+        return getDevice().installPackage(mBuildHelper.getTestFile(apk), replace,
+                instant ? "--ephemeral" : "");
+    }
+
+    private String uninstallPackage(String packageName) throws DeviceNotAvailableException {
+        return getDevice().uninstallPackage(packageName);
+    }
+
+    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
+            throws DeviceNotAvailableException {
+        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
index af53e03..5dae473 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
@@ -15,7 +15,8 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.ephemeralapp1">
+    package="com.android.cts.ephemeralapp1"
+    android:targetSandboxVersion="2">
 
     <application
         android:label="@string/app_name">
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
index 8be8549..5475605 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
@@ -15,7 +15,8 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.ephemeralapp2">
+    package="com.android.cts.ephemeralapp2"
+    android:targetSandboxVersion="2">
 
   <!-- TEST: exists only for testing ephemeral app1 can't see this app -->
   <application
diff --git a/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.mk b/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.mk
new file mode 100644
index 0000000..101d564
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.mk
@@ -0,0 +1,35 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_PACKAGE_NAME := CtsInstantCookieApp
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/InstantCookieApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/InstantCookieApp/AndroidManifest.xml
new file mode 100644
index 0000000..7a937d5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/InstantCookieApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="test.instant.cookie"
+        android:versionCode="1"
+        android:versionName="1.0">
+
+    <application/>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="test.instant.cookie" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/InstantCookieApp/src/test/instant/cookie/CookieTest.java b/hostsidetests/appsecurity/test-apps/InstantCookieApp/src/test/instant/cookie/CookieTest.java
new file mode 100644
index 0000000..37f6bba
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/InstantCookieApp/src/test/instant/cookie/CookieTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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 test.instant.cookie;
+
+import android.content.pm.PackageManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class CookieTest {
+    @Test
+    public void testCookieUpdateAndRetrieval() throws Exception {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // We should be an instant app
+        assertTrue(pm.isInstantApp());
+
+        // The max cookie size is greater than zero
+        assertTrue(pm.getInstantAppCookieMaxSize() > 0);
+
+        // Initially there is no cookie
+        byte[] cookie = pm.getInstantAppCookie();
+        assertTrue(cookie != null && cookie.length == 0);
+
+        // Setting a cookie below max size should work
+        assertTrue(pm.setInstantAppCookie("1".getBytes()));
+
+        // Setting a cookie above max size should not work
+        assertFalse(pm.setInstantAppCookie(
+                new byte[pm.getInstantAppCookieMaxSize() + 1]));
+
+        // Ensure cookie not modified
+        assertEquals("1", new String(pm.getInstantAppCookie()));
+    }
+
+    @Test
+    public void testCookiePersistedAcrossInstantInstalls1() throws Exception {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // Set a cookie to later check when reinstalled as instant app
+        assertTrue(pm.setInstantAppCookie("2".getBytes()));
+    }
+
+    @Test
+    public void testCookiePersistedAcrossInstantInstalls2() throws Exception {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // After the upgrade the cookie should be the same
+        assertEquals("2", new String(pm.getInstantAppCookie()));
+    }
+
+    @Test
+    public void testCookiePersistedUpgradeFromInstant1() throws Exception {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // Make sure we are an instant app
+        assertTrue(pm.isInstantApp());
+
+        // Set a cookie to later check when upgrade to a normal app
+        assertTrue(pm.setInstantAppCookie("3".getBytes()));
+    }
+
+    @Test
+    public void testCookiePersistedUpgradeFromInstant2() throws Exception {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // Make sure we are not an instant app
+        assertFalse(pm.isInstantApp());
+
+        // The cookie survives the upgrade to a normal app
+        assertEquals("3", new String(pm.getInstantAppCookie()));
+    }
+
+    @Test
+    public void testCookieResetOnNonInstantReinstall1() throws Exception {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // Set a cookie to later check when reinstalled as normal app
+        assertTrue(pm.setInstantAppCookie("4".getBytes()));
+    }
+
+    @Test
+    public void testCookieResetOnNonInstantReinstall2() throws Exception {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // The cookie should have been wiped if non-instant app is uninstalled
+        byte[] cookie = pm.getInstantAppCookie();
+        assertTrue(cookie != null && cookie.length == 0);
+    }
+}
diff --git a/hostsidetests/bootstats/Android.mk b/hostsidetests/bootstats/Android.mk
new file mode 100644
index 0000000..ca491e0
--- /dev/null
+++ b/hostsidetests/bootstats/Android.mk
@@ -0,0 +1,34 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := CtsBootStatsTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+LOCAL_CTS_TEST_PACKAGE := android.bootstats
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/bootstats/AndroidTest.xml b/hostsidetests/bootstats/AndroidTest.xml
new file mode 100644
index 0000000..b4e6056
--- /dev/null
+++ b/hostsidetests/bootstats/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for the CTS Boot Stats host tests">
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsBootStatsTestCases.jar" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
new file mode 100644
index 0000000..f7bbd02
--- /dev/null
+++ b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.bootstats.cts;
+
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import junit.framework.Assert;
+
+/**
+ * Set of tests that verify statistics collection during boot.
+ */
+public class BootStatsHostTest extends DeviceTestCase {
+    private static final String TAG = "BootStatsHostTest";
+
+    public void testBootStats() throws Exception {
+        long startTime = System.currentTimeMillis();
+        // Clear buffer to make it easier to find new logs
+        getDevice().executeShellCommand("logcat --buffer=events --clear");
+
+        // reboot device
+        getDevice().rebootUntilOnline();
+        waitForBootCompleted();
+        int upperBoundSeconds = (int) ((System.currentTimeMillis() - startTime) / 1000);
+
+        // wait for logs to post
+        Thread.sleep(10000);
+
+        // find logs and parse them
+        final String log = getDevice().executeShellCommand("logcat --buffer=events -d");
+        int counter = log.indexOf("[boot_complete,");
+        Assert.assertTrue("did not find boot logs", counter != -1);
+        counter += 15;
+        String valueString = log.substring(counter, log.indexOf("]", counter));
+        int bootTime = Integer.valueOf(valueString);
+        Assert.assertTrue("reported boot time must be less than observed boot time",
+                bootTime < upperBoundSeconds);
+        Assert.assertTrue("reported boot time must be non-zero", bootTime > 0);
+    }
+
+    private boolean isBootCompleted() throws Exception {
+        return "1".equals(getDevice().executeShellCommand("getprop sys.boot_completed").trim());
+    }
+
+    private void waitForBootCompleted() throws Exception {
+        for (int i = 0; i < 45; i++) {
+            if (isBootCompleted()) {
+                return;
+            }
+            Thread.sleep(1000);
+        }
+        throw new AssertionError("System failed to become ready!");
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/AffiliationTest.java b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/AffiliationTest.java
index 0bca687..7a1fa40 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/AffiliationTest.java
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/AffiliationTest.java
@@ -1,3 +1,19 @@
+/*
+ * 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.comp.provisioning;
 
 
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
index 8683ed5..a2d7a60 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -29,7 +29,8 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
     compatibility-device-util \
-    android-support-v4
+    android-support-v4 \
+    android-support-test
 
 LOCAL_SDK_VERSION := test_current
 
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
new file mode 100644
index 0000000..4ccb3c6
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.deviceowner;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class AffiliationTest {
+
+    private DevicePolicyManager mDevicePolicyManager;
+    private ComponentName mAdminComponent;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getContext();
+        mDevicePolicyManager = (DevicePolicyManager)
+                context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mAdminComponent = BaseDeviceOwnerTest.BasicAdminReceiver.getComponentName(context);
+    }
+
+    @Test
+    public void testSetAffiliationId1() {
+        setAffiliationIds(Collections.singletonList("id.number.1"));
+    }
+
+    @Test
+    public void testSetAffiliationId2() {
+        setAffiliationIds(Collections.singletonList("id.number.2"));
+    }
+
+    @Test
+    public void testLockTaskMethodsThrowExceptionIfUnaffiliated() {
+        checkLockTaskMethodsThrow();
+    }
+
+    /** Assumes that the calling user is already affiliated before calling this method */
+    @Test
+    public void testSetLockTaskPackagesClearedIfUserBecomesUnaffiliated() {
+        final String[] packages = {"package1", "package2"};
+        mDevicePolicyManager.setLockTaskPackages(mAdminComponent, packages);
+        assertArrayEquals(packages, mDevicePolicyManager.getLockTaskPackages(mAdminComponent));
+        assertTrue(mDevicePolicyManager.isLockTaskPermitted("package1"));
+        assertFalse(mDevicePolicyManager.isLockTaskPermitted("package3"));
+
+        final List<String> previousAffiliationIds =
+                mDevicePolicyManager.getAffiliationIds(mAdminComponent);
+        try {
+            // Clearing affiliation ids for this user. Lock task methods unavailable.
+            setAffiliationIds(Collections.<String>emptyList());
+            checkLockTaskMethodsThrow();
+            assertFalse(mDevicePolicyManager.isLockTaskPermitted("package1"));
+
+            // Affiliating the user again. Previously set packages have been cleared.
+            setAffiliationIds(previousAffiliationIds);
+            assertEquals(0, mDevicePolicyManager.getLockTaskPackages(mAdminComponent).length);
+            assertFalse(mDevicePolicyManager.isLockTaskPermitted("package1"));
+        } finally {
+            mDevicePolicyManager.setAffiliationIds(mAdminComponent, previousAffiliationIds);
+        }
+    }
+
+    private void setAffiliationIds(List<String> ids) {
+        mDevicePolicyManager.setAffiliationIds(mAdminComponent, ids);
+        assertEquals(ids, mDevicePolicyManager.getAffiliationIds(mAdminComponent));
+    }
+
+    private void checkLockTaskMethodsThrow() {
+        try {
+            mDevicePolicyManager.setLockTaskPackages(mAdminComponent, new String[0]);
+            fail("setLockTaskPackages did not throw expected SecurityException");
+        } catch (SecurityException expected) {
+        }
+        try {
+            mDevicePolicyManager.getLockTaskPackages(mAdminComponent);
+            fail("getLockTaskPackages did not throw expected SecurityException");
+        } catch (SecurityException expected) {
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
index 15dd07f..69874c8 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
@@ -15,24 +15,38 @@
  */
 package com.android.cts.deviceowner;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertArrayEquals;
+
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
-// This is not a standard test of an android activity (such as
-// ActivityInstrumentationTestCase2) as it is attempting to test the actual
-// life cycle and how it is affected by lock task, rather than mock intents
-// and setup.
-public class LockTaskTest extends BaseDeviceOwnerTest {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class LockTaskTest {
 
     private static final String TAG = "LockTaskTest";
 
+    private static final String PACKAGE_NAME = LockTaskTest.class.getPackage().getName();
+    private static final ComponentName ADMIN_COMPONENT =
+            new ComponentName(PACKAGE_NAME, BaseDeviceOwnerTest.BasicAdminReceiver.class.getName());
     private static final String TEST_PACKAGE = "com.google.android.example.somepackage";
 
     private static final String UTILITY_ACTIVITY
@@ -117,12 +131,17 @@
     private final Object mActivityResumedLock = new Object();
     private final Object mReceivingActivityCreatedLock = new Object();
 
+    private Context mContext;
     private ActivityManager mActivityManager;
+    private DevicePolicyManager mDevicePolicyManager;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+
+        mDevicePolicyManager = (DevicePolicyManager)
+                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         IntentFilter filter = new IntentFilter();
         filter.addAction(LockTaskUtilityActivity.CREATE_ACTION);
@@ -134,25 +153,29 @@
         mContext.registerReceiver(mReceiver, filter);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+    @After
+    public void tearDown() {
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
         mContext.unregisterReceiver(mReceiver);
-        super.tearDown();
     }
 
     // Setting and unsetting the lock task packages.
+    @Test
     public void testSetLockTaskPackages() {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { TEST_PACKAGE });
+        final String[] packages = new String[] { TEST_PACKAGE, "some.other.package" };
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, packages);
+        assertArrayEquals(packages, mDevicePolicyManager.getLockTaskPackages(ADMIN_COMPONENT));
         assertTrue(mDevicePolicyManager.isLockTaskPermitted(TEST_PACKAGE));
 
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
+        assertEquals(0, mDevicePolicyManager.getLockTaskPackages(ADMIN_COMPONENT).length);
         assertFalse(mDevicePolicyManager.isLockTaskPermitted(TEST_PACKAGE));
     }
 
     // Start lock task, verify that ActivityManager knows thats what is going on.
+    @Test
     public void testStartLockTask() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
         startLockTask(UTILITY_ACTIVITY);
         waitForResume();
 
@@ -166,8 +189,9 @@
 
     // Verifies that the act of finishing is blocked by ActivityManager in lock task.
     // This results in onDestroy not being called until stopLockTask is called before finish.
+    @Test
     public void testCannotFinish() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
         startLockTask(UTILITY_ACTIVITY);
 
         // If lock task has not exited then the activity shouldn't actually receive onDestroy.
@@ -179,11 +203,12 @@
     }
 
     // Verifies that updating the whitelisting during lock task mode finishes the locked task.
+    @Test
     public void testUpdateWhitelisting() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
         startLockTask(UTILITY_ACTIVITY);
 
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
 
         synchronized (mActivityRunningLock) {
             mActivityRunningLock.wait(ACTIVITY_DESTROYED_TIMEOUT_MILLIS);
@@ -196,8 +221,9 @@
 
     // This launches an activity that is in the current task.
     // This should always be permitted as a part of lock task (since it isn't a new task).
+    @Test
     public void testStartActivity_withinTask() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
         startLockTask(UTILITY_ACTIVITY);
         waitForResume();
 
@@ -216,8 +242,9 @@
 
     // This launches a whitelisted activity that is not part of the current task.
     // This should be permitted as a part of lock task.
+    @Test
     public void testStartActivity_outsideTaskWhitelisted() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME,
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME,
                 RECEIVING_ACTIVITY_PACKAGE_NAME });
         startLockTask(UTILITY_ACTIVITY);
         waitForResume();
@@ -235,8 +262,9 @@
 
     // This launches a non-whitelisted activity that is not part of the current task.
     // This should be blocked.
+    @Test
     public void testStartActivity_outsideTaskNonWhitelisted() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
         startLockTask(UTILITY_ACTIVITY);
         waitForResume();
 
@@ -251,8 +279,9 @@
 
     // Test the lockTaskMode flag for an activity declaring if_whitelisted.
     // Whitelist the activity and verify that lock task mode is started.
+    @Test
     public void testManifestArgument_whitelisted() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
         startAndWait(getLockTaskUtility(UTILITY_ACTIVITY_IF_WHITELISTED));
         waitForResume();
 
@@ -265,6 +294,7 @@
 
     // Test the lockTaskMode flag for an activity declaring if_whitelisted.
     // Don't whitelist the activity and verify that lock task mode is not started.
+    @Test
     public void testManifestArgument_nonWhitelisted() throws Exception {
         startAndWait(getLockTaskUtility(UTILITY_ACTIVITY_IF_WHITELISTED));
         waitForResume();
@@ -278,8 +308,9 @@
 
     // Test the lockTaskMode flag for an activity declaring if_whitelisted.
     // An activity locked via manifest argument cannot finish without calling stopLockTask.
+    @Test
     public void testManifestArgument_cannotFinish() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
         startAndWait(getLockTaskUtility(UTILITY_ACTIVITY_IF_WHITELISTED));
         waitForResume();
 
@@ -293,12 +324,13 @@
 
     // Test the lockTaskMode flag for an activity declaring if_whitelisted.
     // Verifies that updating the whitelisting during lock task mode finishes the locked task.
+    @Test
     public void testManifestArgument_updateWhitelisting() throws Exception {
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
         startAndWait(getLockTaskUtility(UTILITY_ACTIVITY_IF_WHITELISTED));
         waitForResume();
 
-        mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
 
         synchronized (mActivityRunningLock) {
             mActivityRunningLock.wait(ACTIVITY_DESTROYED_TIMEOUT_MILLIS);
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index d7eaf92..994dc62 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -47,6 +47,11 @@
         </activity>
         <service android:name=".SimpleService" android:exported="true">
         </service>
+        <receiver android:name=".SimpleReceiver" android:exported="true">
+        </receiver>
+        <receiver android:name=".SimpleRemoteReceiver" android:process=":receiver"
+                  android:exported="true">
+        </receiver>
     </application>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleReceiver.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleReceiver.java
new file mode 100644
index 0000000..b82d04d
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * 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.launcherapps.simpleapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class SimpleReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        //Log.i("xxx", "SimpleReceiver: " + intent);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleRemoteReceiver.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleRemoteReceiver.java
new file mode 100644
index 0000000..de767d8
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleRemoteReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * 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.launcherapps.simpleapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class SimpleRemoteReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        //Log.i("xxx", "SimpleRemoteReceiver: " + intent);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java b/hostsidetests/devicepolicy/app/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java
index 7a5dee3..edd9de6 100644
--- a/hostsidetests/devicepolicy/app/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java
+++ b/hostsidetests/devicepolicy/app/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java
@@ -18,6 +18,8 @@
 
 import android.R;
 import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -36,6 +38,7 @@
 
     private static String TAG = "ReflectorVpnService";
     private static final int NOTIFICATION_ID = 1;
+    private static final String NOTIFICATION_CHANNEL_ID = TAG;
     private static int MTU = 1799;
 
     private ParcelFileDescriptor mFd = null;
@@ -50,7 +53,11 @@
     public int onStartCommand(Intent intent, int flags, int startId) {
         // Put ourself in the foreground to stop the system killing us while we wait for orders from
         // the hostside test.
-        startForeground(NOTIFICATION_ID, new Notification.Builder(this)
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        notificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
+        startForeground(NOTIFICATION_ID, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
                 .setSmallIcon(R.drawable.ic_dialog_alert)
                 .build());
         start();
@@ -60,6 +67,8 @@
     @Override
     public void onDestroy() {
         stop();
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
         super.onDestroy();
     }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index bd32885..bf5049c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -323,7 +323,7 @@
     /** Reboots the device and block until the boot complete flag is set. */
     protected void rebootAndWaitUntilReady() throws DeviceNotAvailableException {
         getDevice().executeShellCommand("reboot");
-        assertTrue("Device failed to boot", getDevice().waitForBootComplete(60000));
+        assertTrue("Device failed to boot", getDevice().waitForBootComplete(120000));
     }
 
     /** Returns true if the system supports the split between system and primary user. */
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 94e528b..43ca4e0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -255,6 +255,14 @@
         executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState");
     }
 
+    /**
+     * Require a device for tests that use the network stack. Headless Androids running in
+     * data centres might need their network rules un-tampered-with in order to keep the ADB / VNC
+     * connection alive.
+     *
+     * This is only a problem on device owner / profile owner running on USER_SYSTEM, because
+     * network rules for this user will affect UID 0.
+     */
     @RequiresDevice
     public void testAlwaysOnVpn() throws Exception {
         if (!mHasFeature) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 676f455..80aa652 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -16,9 +16,6 @@
 
 package com.android.cts.devicepolicy;
 
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -343,7 +340,7 @@
         executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval");
     }
 
-    public void testLockTask() throws Exception {
+    public void testLockTask_deviceOwnerUser() throws Exception {
         if (!mHasFeature) {
             return;
         }
@@ -361,6 +358,52 @@
         }
     }
 
+    public void testLockTask_unaffiliatedUser() throws Exception {
+        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
+            return;
+        }
+
+        final int userId = createUser();
+        installAppAsUser(DEVICE_OWNER_APK, userId);
+        setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
+
+        runDeviceTestsAsUser(
+                DEVICE_OWNER_PKG,
+                ".AffiliationTest",
+                "testLockTaskMethodsThrowExceptionIfUnaffiliated",
+                userId);
+
+        runDeviceTestsAsUser(
+                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", mPrimaryUserId);
+        runDeviceTestsAsUser(
+                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", userId);
+        runDeviceTestsAsUser(
+                DEVICE_OWNER_PKG,
+                ".AffiliationTest",
+                "testSetLockTaskPackagesClearedIfUserBecomesUnaffiliated",
+                userId);
+    }
+
+    public void testLockTask_affiliatedSecondaryUser() throws Exception {
+        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
+            return;
+        }
+
+        final int userId = createUser();
+        installAppAsUser(INTENT_RECEIVER_APK, userId);
+        installAppAsUser(DEVICE_OWNER_APK, userId);
+        setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
+
+        switchUser(userId);
+
+        // Setting the same affiliation ids on both users and running the lock task tests.
+        runDeviceTestsAsUser(
+                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", mPrimaryUserId);
+        runDeviceTestsAsUser(
+                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", userId);
+        runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".LockTaskTest", userId);
+    }
+
     public void testSystemUpdatePolicy() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 212671a..01e55fe 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -57,29 +57,5 @@
         super.tearDown();
     }
 
-    /**
-     * Require a device for tests that use the network stack. Headless Android setups running in
-     * data centres may need their network rules un-tampered-with in order to keep the ADB / VNC
-     * connection alive.
-     *
-     * This is only a problem on device owner / profile owner running on USER_SYSTEM, because
-     * network rules for this user will affect UID 0.
-     */
-
-    @Override @RequiresDevice
-    public void testAlwaysOnVpn() throws Exception {
-        super.testAlwaysOnVpn();
-    }
-
-    @Override @RequiresDevice
-    public void testAlwaysOnVpnLockDown() throws Exception {
-        super.testAlwaysOnVpnLockDown();
-    }
-
-    @Override @RequiresDevice
-    public void testAlwaysOnVpnPackageUninstalled() throws Exception {
-        super.testAlwaysOnVpnPackageUninstalled();
-    }
-
-    // All other tests for this class are defined in DeviceAndProfileOwnerTest
+    // All tests for this class are defined in DeviceAndProfileOwnerTest
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 81b1b54..ccc95be 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -131,4 +131,23 @@
         // DISALLOW_UNMUTE_MICROPHONE and DISALLOW_ADJUST_VOLUME can only be set by device owners
         // and profile owners on the primary user.
     }
+
+    /**
+     * Don't require a device for tests that use the network stack on secondary users.
+     */
+    @Override
+    public void testAlwaysOnVpn() throws Exception {
+        super.testAlwaysOnVpn();
+    }
+
+    @Override
+    public void testAlwaysOnVpnLockDown() throws Exception {
+        super.testAlwaysOnVpnLockDown();
+    }
+
+    @Override
+    public void testAlwaysOnVpnPackageUninstalled() throws Exception {
+        super.testAlwaysOnVpnPackageUninstalled();
+    }
+
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
index d7267b6..803cf36 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
@@ -50,28 +50,4 @@
         }
         super.tearDown();
     }
-
-    /**
-     * Require a device for tests that use the network stack. Headless Android setups running in
-     * data centres may need their network rules un-tampered-with in order to keep the ADB / VNC
-     * connection alive.
-     *
-     * This is only a problem on device owner / profile owner running on USER_SYSTEM, because
-     * network rules for this user will affect UID 0.
-     */
-
-    @Override @RequiresDevice
-    public void testAlwaysOnVpn() throws Exception {
-        super.testAlwaysOnVpn();
-    }
-
-    @Override @RequiresDevice
-    public void testAlwaysOnVpnLockDown() throws Exception {
-        super.testAlwaysOnVpnLockDown();
-    }
-
-    @Override @RequiresDevice
-    public void testAlwaysOnVpnPackageUninstalled() throws Exception {
-        super.testAlwaysOnVpnPackageUninstalled();
-    }
 }
diff --git a/hostsidetests/dumpsys/apps/Android.mk b/hostsidetests/dumpsys/apps/Android.mk
new file mode 100644
index 0000000..4a74e80
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/Android.mk
@@ -0,0 +1,20 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/dumpsys/FramestatsTestApp/Android.mk b/hostsidetests/dumpsys/apps/FramestatsTestApp/Android.mk
similarity index 100%
rename from hostsidetests/dumpsys/FramestatsTestApp/Android.mk
rename to hostsidetests/dumpsys/apps/FramestatsTestApp/Android.mk
diff --git a/hostsidetests/dumpsys/FramestatsTestApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/dumpsys/FramestatsTestApp/AndroidManifest.xml
rename to hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
diff --git a/hostsidetests/dumpsys/FramestatsTestApp/src/com/android/cts/framestatstestapp/FramestatsTestAppActivity.java b/hostsidetests/dumpsys/apps/FramestatsTestApp/src/com/android/cts/framestatstestapp/FramestatsTestAppActivity.java
similarity index 100%
rename from hostsidetests/dumpsys/FramestatsTestApp/src/com/android/cts/framestatstestapp/FramestatsTestAppActivity.java
rename to hostsidetests/dumpsys/apps/FramestatsTestApp/src/com/android/cts/framestatstestapp/FramestatsTestAppActivity.java
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk
new file mode 100644
index 0000000..dc9e4c9
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk
@@ -0,0 +1,39 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsProcStatsHelperApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+    android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
new file mode 100644
index 0000000..3944763
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.cts.procstatshelper"
+    android:versionCode="32123">
+
+    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="24"/>
+
+    <application>
+        <activity android:name=".MainActivity"
+            android:exported="true" />
+        <activity android:name=".FlashingActivity"
+            android:exported="true" />
+        <service android:name=".ProcStatsHelperServiceMain"
+            android:exported="true" />
+        <service android:name=".ProcStatsHelperServiceSub" android:process=":proc2"
+            android:exported="true" />
+    </application>
+</manifest>
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/FlashingActivity.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/FlashingActivity.java
new file mode 100644
index 0000000..7dcce60
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/FlashingActivity.java
@@ -0,0 +1,69 @@
+/*
+ * 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.server.cts.procstatshelper;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class FlashingActivity extends Activity {
+    private static final String TAG = "FlashingActivity";
+
+    private static Semaphore mGate = new Semaphore(1);
+
+    @Override
+    protected void onResume() {
+        Log.i(TAG, "onResume");
+        super.onResume();
+
+        new Handler().postDelayed(() -> finish(), 100);
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(TAG, "onDestroy");
+        super.onDestroy();
+
+        mGate.release();
+    }
+
+    public static void flash(Context context) {
+        Log.i(TAG, "flash");
+        try {
+            mGate.acquire();
+            final Intent intent = new Intent()
+                    .setComponent(new ComponentName(context, FlashingActivity.class))
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                            | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+            context.startActivity(intent);
+
+            if (!mGate.tryAcquire(10, TimeUnit.SECONDS)) {
+                throw new RuntimeException("Activity didn't start.");
+            }
+            mGate.release();
+
+        } catch (InterruptedException e) {
+            Log.e(TAG, "InterruptedException", e);
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/MainActivity.java
similarity index 67%
copy from hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
copy to hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/MainActivity.java
index 4e3fc89..40e657c 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/MainActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,18 +11,19 @@
  * 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
+ * limitations under the License.
  */
-
-package android.server.cts;
+package com.android.server.cts.procstatshelper;
 
 import android.app.Activity;
-import android.graphics.Rect;
+import android.os.Handler;
 
-public class LaunchTapToFinishPipActivity extends Activity {
+public class MainActivity extends Activity {
     @Override
     protected void onResume() {
         super.onResume();
-        PipActivity.launchActivity(this, new Rect(0, 0, 500, 500), true /* tapToLaunch */);
+
+        // Close in 1 second.
+        new Handler().postDelayed(() -> finish(), 1000);
     }
 }
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceBase.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceBase.java
new file mode 100644
index 0000000..2ee5628
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceBase.java
@@ -0,0 +1,59 @@
+/*
+ * 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.server.cts.procstatshelper;
+
+import android.app.IntentService;
+import android.app.Notification;
+import android.content.Intent;
+import android.util.Log;
+
+public class ProcStatsHelperServiceBase extends IntentService {
+    private static final String TAG = "ProcStatsHelperService";
+
+    public ProcStatsHelperServiceBase() {
+        super(TAG);
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Log.e(TAG, "onCreate: " + getClass().getName());
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        Log.e(TAG, "onHandleIntent: " + getClass().getName());
+
+        // Run as a background service for 500 ms.
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Caught exception", e);
+        }
+
+        Notification notification = new Notification.Builder(getApplicationContext())
+                .setContentTitle("FgService")
+                .setSmallIcon(android.R.drawable.ic_popup_sync)
+                .build();
+        startForeground(1, notification);
+        // Run as a foreground service for 1 second.
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Caught exception", e);
+        }
+    }
+}
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceMain.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceMain.java
new file mode 100644
index 0000000..5fffc21
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceMain.java
@@ -0,0 +1,19 @@
+/*
+ * 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.server.cts.procstatshelper;
+
+public class ProcStatsHelperServiceMain extends ProcStatsHelperServiceBase {
+}
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceSub.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceSub.java
new file mode 100644
index 0000000..63e94b9
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceSub.java
@@ -0,0 +1,19 @@
+/*
+ * 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.server.cts.procstatshelper;
+
+public class ProcStatsHelperServiceSub extends ProcStatsHelperServiceBase {
+}
diff --git a/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk b/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk
new file mode 100644
index 0000000..1691103
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk
@@ -0,0 +1,39 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsProcStatsApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+    android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/dumpsys/apps/ProcStatsTestApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/ProcStatsTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..56cacb5
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsTestApp/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.cts.procstats" >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.cts.procstats" />
+</manifest>
diff --git a/hostsidetests/dumpsys/apps/ProcStatsTestApp/src/com/android/server/cts/procstats/ProcStatsTest.java b/hostsidetests/dumpsys/apps/ProcStatsTestApp/src/com/android/server/cts/procstats/ProcStatsTest.java
new file mode 100644
index 0000000..c27eb1e
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsTestApp/src/com/android/server/cts/procstats/ProcStatsTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.server.cts.procstats;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.fail;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Used by NetstatsIncidentTest.  Makes some network requests so "dumpsys netstats" will have
+ * something to show.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ProcStatsTest {
+    private static final String TAG = "ProcStatsTest";
+
+    @After
+    public void tearDown() {
+        runCommand("dumpsys procstats --stop-pretend-screen", "^$");
+    }
+
+    @Test
+    public void testLaunchApp() throws Exception {
+
+        InstrumentationRegistry.getContext().startActivity(new Intent()
+                .setComponent(ComponentName.unflattenFromString(
+                        "com.android.server.cts.procstatshelper/.MainActivity")));
+
+        Thread.sleep(3000);
+
+        InstrumentationRegistry.getContext().startService(new Intent()
+                .setComponent(ComponentName.unflattenFromString(
+                        "com.android.server.cts.procstatshelper/.ProcStatsHelperServiceMain")));
+
+        Thread.sleep(3000);
+
+        InstrumentationRegistry.getContext().startService(new Intent()
+                .setComponent(ComponentName.unflattenFromString(
+                        "com.android.server.cts.procstatshelper/.ProcStatsHelperServiceSub")));
+
+        Thread.sleep(3000);
+
+        // Now run something with the screen off.
+        runCommand("dumpsys procstats --pretend-screen-off", "^$");
+
+        InstrumentationRegistry.getContext().startActivity(new Intent()
+                .setComponent(ComponentName.unflattenFromString(
+                        "com.android.server.cts.procstatshelper/.MainActivity")));
+
+        Thread.sleep(3000);
+
+        // run "dumpsys meminfo" to update the PSS stats.
+        runCommand("dumpsys meminfo com.android.server.cts.procstatshelper",
+                "MEMINFO in pid");
+        runCommand("dumpsys meminfo com.android.server.cts.procstatshelper:proc2",
+                "MEMINFO in pid");
+    }
+
+    static List<String> readAll(ParcelFileDescriptor pfd) {
+        try {
+            try {
+                final ArrayList<String> ret = new ArrayList<>();
+                try (BufferedReader r = new BufferedReader(
+                        new FileReader(pfd.getFileDescriptor()))) {
+                    String line;
+                    while ((line = r.readLine()) != null) {
+                        ret.add(line);
+                    }
+                    r.readLine();
+                }
+                return ret;
+            } finally {
+                pfd.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    static String concatResult(List<String> result) {
+        final StringBuilder sb = new StringBuilder();
+        for (String s : result) {
+            sb.append(s);
+            sb.append("\n");
+        }
+        return sb.toString().trim();
+    }
+
+    private void runCommand(String command, String expectedOutputRegex) {
+        Log.i(TAG, "Running comamnd: " + command);
+        final String result = concatResult(readAll(
+                InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                        command)));
+        Log.i(TAG, "Output:");
+        Log.i(TAG, result);
+        if (!Pattern.compile(expectedOutputRegex).matcher(result).find()) {
+            fail("Expected=" + expectedOutputRegex + "\nBut was=" + result);
+        }
+    }
+}
+
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
index aecb401..9258d433 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
@@ -16,16 +16,35 @@
 
 package android.dumpsys.cts;
 
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
+import java.io.FileNotFoundException;
+import java.util.Map;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 public class BaseDumpsysTest extends DeviceTestCase implements IBuildReceiver {
     protected static final String TAG = "DumpsysHostTest";
 
+    private static final String TEST_RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
     /**
      * A reference to the device under test.
      */
@@ -54,6 +73,47 @@
         }
     }
 
+    protected static long assertNonNegativeInteger(String input) {
+        try {
+            final long result = Long.parseLong(input);
+            assertTrue("Expected non-negative, but was: " + result, result >= 0);
+
+            return result;
+        } catch (NumberFormatException e) {
+            fail("Expected an integer but found \"" + input + "\"");
+            // Won't be hit, above throws AssertException
+            return -1;
+        }
+    }
+
+    protected static long assertPositiveInteger(String input) {
+        try {
+            final long result = Long.parseLong(input);
+            assertTrue("Expected positive, but was: " + result, result > 0);
+
+            return result;
+        } catch (NumberFormatException e) {
+            fail("Expected an integer but found \"" + input + "\"");
+            // Won't be hit, above throws AssertException
+            return -1;
+        }
+    }
+
+    protected static void assertMinAvgMax(String min, String avg, String max, boolean checkAvg) {
+        final long lMin = assertNonNegativeInteger(min);
+        final long lAvg = assertNonNegativeInteger(avg);
+        final long lMax = assertNonNegativeInteger(max);
+
+        if (checkAvg) {
+            assertTrue("min [" + min + "] <= avg [" + avg + "]", lMin <= lAvg);
+            assertTrue("avg [" + avg + "] <= max [" + max + "]", lAvg <= lMax);
+        } else {
+            // There was a bug in the average calculation, so we can't check the average
+            // from the last N hour stats, which may be generated on with the buggy logic.
+            assertTrue("min [" + min + "] <= max [" + max + "]", lMin <= lMax);
+        }
+    }
+
     protected static double assertDouble(String input) {
         try {
             return Double.parseDouble(input);
@@ -66,4 +126,93 @@
     protected static void assertSeenTag(Set<String> seenTags, String tag) {
         assertTrue("No line starting with \"" + tag + ",\"", seenTags.contains(tag));
     }
+
+
+    /**
+     * Install a device side test package.
+     *
+     * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
+     * @param grantPermissions whether to give runtime permissions.
+     */
+    protected void installPackage(String appFileName, boolean grantPermissions)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.d("Installing app " + appFileName);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        final String result = getDevice().installPackage(
+                buildHelper.getTestFile(appFileName), true, grantPermissions);
+        assertNull("Failed to install " + appFileName + ": " + result, result);
+    }
+
+    /**
+     * Run a device side test.
+     *
+     * @param pkgName Test package name, such as "com.android.server.cts.netstats".
+     * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
+     * @param testMethodName Test method name.
+     * @throws DeviceNotAvailableException
+     */
+    protected void runDeviceTests(@Nonnull String pkgName,
+            @Nullable String testClassName, @Nullable String testMethodName)
+            throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+                pkgName, TEST_RUNNER, getDevice().getIDevice());
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        CollectingTestListener listener = new CollectingTestListener();
+        assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
+
+        final TestRunResult result = listener.getCurrentRunResults();
+        if (result.isRunFailure()) {
+            throw new AssertionError("Failed to successfully run device tests for "
+                    + result.getName() + ": " + result.getRunFailureMessage());
+        }
+        if (result.getNumTests() == 0) {
+            throw new AssertionError("No tests were run on the device");
+        }
+
+        if (result.hasFailedTests()) {
+            // build a meaningful error message
+            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+                    result.getTestResults().entrySet()) {
+                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+    }
+
+    /**
+     * Execute the given command, and find the given pattern and return the resulting
+     * {@link Matcher}.
+     */
+    protected Matcher execCommandAndFind(String command, String pattern) throws Exception {
+        final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        getDevice().executeShellCommand(command, receiver);
+        final String output = receiver.getOutput();
+        final Matcher matcher = Pattern.compile(pattern).matcher(output);
+        assertTrue("Pattern '" + pattern + "' didn't match. Output=\n" + output, matcher.find());
+        return matcher;
+    }
+
+    /**
+     * Execute the given command, find the given pattern, and return the first captured group
+     * as a String.
+     */
+    protected String execCommandAndGetFirstGroup(String command, String pattern) throws Exception {
+        final Matcher matcher = execCommandAndFind(command, pattern);
+        assertTrue("No group found for pattern '" + pattern + "'", matcher.groupCount() > 0);
+        return matcher.group(1);
+    }
 }
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 9c17248..f69ceab 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -237,10 +237,11 @@
     }
 
     private void checkSensor(String[] parts) {
-        assertEquals(7, parts.length);
+        assertEquals(8, parts.length);
         assertInteger(parts[4]); // sensorNumber
         assertInteger(parts[5]); // totalTime
         assertInteger(parts[6]); // count
+        assertInteger(parts[7]); // backgroundCount
     }
 
     private void checkVibrator(String[] parts) {
@@ -256,24 +257,36 @@
     }
 
     private void checkStateTime(String[] parts) {
-        assertEquals(7, parts.length);
-        assertInteger(parts[4]); // foreground
-        assertInteger(parts[5]); // active
-        assertInteger(parts[6]); // running
+        assertEquals(10, parts.length);
+        assertInteger(parts[4]); // top
+        assertInteger(parts[5]); // foreground_service
+        assertInteger(parts[6]); // top_sleeping
+        assertInteger(parts[7]); // foreground
+        assertInteger(parts[8]); // background
+        assertInteger(parts[9]); // cached
     }
 
     private void checkWakelock(String[] parts) {
-        assertEquals(14, parts.length);
+        assertEquals(20, parts.length);
         assertNotNull(parts[4]);      // wakelock
+
         assertInteger(parts[5]);      // full totalTime
         assertEquals("f", parts[6]);  // full
         long full_count = assertInteger(parts[7]);      // full count
-        assertInteger(parts[8]);      // partial totalTime
-        assertEquals("p", parts[9]);  // partial
-        long partial_count = assertInteger(parts[10]);     // partial count
-        assertInteger(parts[11]);     // window totalTime
-        assertEquals("w", parts[12]); // window
-        long window_count = assertInteger(parts[13]);     // window count
+        assertInteger(parts[8]);      // current
+        assertInteger(parts[9]);      // max
+
+        assertInteger(parts[10]);      // partial totalTime
+        assertEquals("p", parts[11]);  // partial
+        long partial_count = assertInteger(parts[12]);     // partial count
+        assertInteger(parts[13]);      // current
+        assertInteger(parts[14]);      // max
+
+        assertInteger(parts[15]);     // window totalTime
+        assertEquals("w", parts[16]); // window
+        long window_count = assertInteger(parts[17]);     // window count
+        assertInteger(parts[18]);      // current
+        assertInteger(parts[19]);     // max
 
         // Sanity checks.
         assertTrue("full wakelock count must be >= 0", full_count >= 0);
@@ -312,7 +325,7 @@
     }
 
     private void checkNetwork(String[] parts) {
-        assertEquals(14, parts.length);
+        assertEquals(18, parts.length);
         long mbRx = assertInteger(parts[4]);  // mobileBytesRx
         long mbTx = assertInteger(parts[5]);  // mobileBytesTx
         long wbRx = assertInteger(parts[6]);  // wifiBytesRx
@@ -323,6 +336,10 @@
         long wpTx = assertInteger(parts[11]); // wifiPacketsTx
         assertInteger(parts[12]); // mobileActiveTime (usec)
         assertInteger(parts[13]); // mobileActiveCount
+        assertInteger(parts[14]); // btBytesRx
+        assertInteger(parts[15]); // btBytesTx
+        assertInteger(parts[16]); // mobileWakeup
+        assertInteger(parts[17]); // wifiWakeup
 
         // Assuming each packet contains some bytes, bytes >= packets >= 0.
         assertTrue("mobileBytesRx must be >= mobilePacketsRx", mbRx >= mpRx);
@@ -336,14 +353,15 @@
     }
 
     private void checkUserActivity(String[] parts) {
-        assertEquals(7, parts.length);
+        assertEquals(8, parts.length);
         assertInteger(parts[4]); // other
         assertInteger(parts[5]); // button
         assertInteger(parts[6]); // touch
+        assertInteger(parts[7]); // accessibility
     }
 
     private void checkBattery(String[] parts) {
-        assertEquals(12, parts.length);
+        assertEquals(13, parts.length);
         if (!parts[4].equals("N/A")) {
             assertInteger(parts[4]);  // startCount
         }
@@ -354,7 +372,7 @@
         assertInteger(parts[9]);  // startClockTime
         long bOffReal = assertInteger(parts[10]); // batteryScreenOffRealtime
         long bOffUp = assertInteger(parts[11]); // batteryScreenOffUptime
-
+        long bEstCap = assertInteger(parts[12]); // batteryEstimatedCapacity
         // The device cannot be up more than there are real-world seconds.
         assertTrue("batteryRealtime must be >= batteryUptime", bReal >= bUp);
         assertTrue("totalRealtime must be >= totalUptime", tReal >= tUp);
@@ -368,14 +386,17 @@
         assertTrue("totalUptime must be >= batteryUptime", tUp >= bUp);
         assertTrue("batteryUptime must be >= batteryScreenOffUptime", bUp >= bOffUp);
         assertTrue("batteryScreenOffUptime must be >= 0", bOffUp >= 0);
+        assertTrue("batteryEstimatedCapacity must be >= 0", bEstCap >= 0);
     }
 
     private void checkBatteryDischarge(String[] parts) {
-        assertEquals(8, parts.length);
+        assertEquals(10, parts.length);
         assertInteger(parts[4]); // low
         assertInteger(parts[5]); // high
         assertInteger(parts[6]); // screenOn
         assertInteger(parts[7]); // screenOff
+        assertInteger(parts[8]); // dischargeCount
+        assertInteger(parts[9]); // dischargeScreenOffCount
     }
 
     private void checkBatteryLevel(String[] parts) {
@@ -411,7 +432,7 @@
     }
 
     private void checkGlobalNetwork(String[] parts) {
-        assertEquals(12, parts.length);
+        assertEquals(14, parts.length);
         assertInteger(parts[4]);  // mobileRxTotalBytes
         assertInteger(parts[5]);  // mobileTxTotalBytes
         assertInteger(parts[6]);  // wifiRxTotalBytes
@@ -420,6 +441,8 @@
         assertInteger(parts[9]);  // mobileTxTotalPackets
         assertInteger(parts[10]); // wifiRxTotalPackets
         assertInteger(parts[11]); // wifiTxTotalPackets
+        assertInteger(parts[12]); // btRxTotalBytes
+        assertInteger(parts[13]); // btTxTotalBytes
     }
 
     private void checkScreenBrightness(String[] parts) {
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
index d78bf93..1e67d6b 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
@@ -16,6 +16,8 @@
 
 package android.dumpsys.cts;
 
+import com.android.tradefed.log.LogUtil.CLog;
+
 import java.io.BufferedReader;
 import java.io.StringReader;
 import java.util.HashSet;
@@ -25,28 +27,54 @@
  * Test to check the format of the dumps of the processstats test.
  */
 public class ProcessStatsDumpsysTest extends BaseDumpsysTest {
+    private static final String DEVICE_SIDE_TEST_APK = "CtsProcStatsApp.apk";
+    private static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.procstats";
+
+    private static final String DEVICE_SIDE_HELPER_APK = "CtsProcStatsHelperApp.apk";
+    private static final String DEVICE_SIDE_HELPER_PACKAGE = "com.android.server.cts.procstatshelper";
+
+    private static final boolean UNINSTALL_APPS_AFTER_TESTS = true; // DON'T SUBMIT WITH TRUE
+
     /**
      * Tests the output of "dumpsys procstats -c". This is a proxy for testing "dumpsys procstats
      * --checkin", since the latter is not idempotent.
-     *
-     * @throws Exception
      */
     public void testProcstatsOutput() throws Exception {
+        // First, run the helper app so that we have some interesting records in the output.
+        checkWithProcStatsApp();
+
         String procstats = mDevice.executeShellCommand("dumpsys procstats -c");
         assertNotNull(procstats);
         assertTrue(procstats.length() > 0);
 
-        Set<String> seenTags = new HashSet<>();
-        int version = -1;
+        final int sep24h = procstats.indexOf("AGGREGATED OVER LAST 24 HOURS:");
+        final int sep3h = procstats.indexOf("AGGREGATED OVER LAST 3 HOURS:");
+
+        assertTrue("24 hour stats not found.", sep24h > 1);
+        assertTrue("3 hour stats not found.", sep3h > 1);
+
+        // Current
+        checkProcStateOutput(procstats.substring(0, sep24h), /*checkAvg=*/ true);
+
+        // Last 24 hours
+        checkProcStateOutput(procstats.substring(sep24h, sep3h), /*checkAvg=*/ false);
+
+        // Last 3 hours
+        checkProcStateOutput(procstats.substring(sep3h), /*checkAvg=*/ false);
+    }
+
+    private void checkProcStateOutput(String text, boolean checkAvg) throws Exception {
+        final Set<String> seenTags = new HashSet<>();
 
         try (BufferedReader reader = new BufferedReader(
-                new StringReader(procstats))) {
+                new StringReader(text))) {
 
             String line;
             while ((line = reader.readLine()) != null) {
                 if (line.isEmpty()) {
                     continue;
                 }
+                CLog.d("Checking line: " + line);
 
                 // extra space to make sure last column shows up.
                 if (line.endsWith(",")) {
@@ -58,34 +86,34 @@
                 switch (parts[0]) {
                     case "vers":
                         assertEquals(2, parts.length);
-                        version = Integer.parseInt(parts[1]);
+                        assertEquals(5, Integer.parseInt(parts[1]));
                         break;
                     case "period":
                         checkPeriod(parts);
                         break;
                     case "pkgproc":
-                        checkPkgProc(parts, version);
+                        checkPkgProc(parts);
                         break;
                     case "pkgpss":
-                        checkPkgPss(parts, version);
+                        checkPkgPss(parts, checkAvg);
                         break;
                     case "pkgsvc-bound":
                     case "pkgsvc-exec":
                     case "pkgsvc-run":
                     case "pkgsvc-start":
-                        checkPkgSvc(parts, version);
+                        checkPkgSvc(parts);
                         break;
                     case "pkgkills":
-                        checkPkgKills(parts, version);
+                        checkPkgKills(parts, checkAvg);
                         break;
                     case "proc":
                         checkProc(parts);
                         break;
                     case "pss":
-                        checkPss(parts);
+                        checkPss(parts, checkAvg);
                         break;
                     case "kills":
-                        checkKills(parts);
+                        checkKills(parts, checkAvg);
                         break;
                     case "total":
                         checkTotal(parts);
@@ -96,60 +124,45 @@
             }
         }
 
-/* more kinds
-total   0n:4426040  0m:5779 1n:6067104  1m:74489
-sysmemusage 0np1791668:791668:791668:40080:40080:40080:1176:1176:1176:147068:147068:147068:230682:230682:230682 ...
-weights 10573412    6.133572860728E12:40  ...
-availablepages  Unmovable   0   69  37  31  14  20  28  16  5   7   0   0
-availablepages  Reclaimable 0   0   0   0   1   1   1   1   0   0   1   0
-availablepages  Movable 0   1   0   1   1   1   2   0   1   1   0   173
-availablepages  Reserve 0   0   0   0   0   0   0   0   0   0   0   2
-availablepages  CMA 0   1   1   98  111 70  35  20  7   9   3   18
-availablepages  Isolate
- */
-
-        // spot check a few tags
+        assertSeenTag(seenTags, "vers");
+        assertSeenTag(seenTags, "period");
         assertSeenTag(seenTags, "pkgproc");
         assertSeenTag(seenTags, "proc");
         assertSeenTag(seenTags, "pss");
         assertSeenTag(seenTags, "total");
+        assertSeenTag(seenTags, "weights");
+        assertSeenTag(seenTags, "availablepages");
     }
 
     private void checkPeriod(String[] parts) {
         assertTrue("Expected 5 or 6, found: " + parts.length,
                 parts.length == 5 || parts.length == 6);
         assertNotNull(parts[1]); // date
-        assertInteger(parts[2]); // start time (msec)
-        assertInteger(parts[3]); // end time (msec)
+        assertNonNegativeInteger(parts[2]); // start time (msec)
+        assertNonNegativeInteger(parts[3]); // end time (msec)
+
+        // TODO Check the values.
         assertNotNull(parts[4]); // status
         if (parts.length == 6) {
             assertNotNull(parts[5]); // swapped-out-pss
         }
     }
 
-    private void checkPkgProc(String[] parts, int version) {
+    private void checkPkgProc(String[] parts) {
         int statesStartIndex;
 
-        if (version < 4) {
-            assertTrue(parts.length >= 4);
-            assertNotNull(parts[1]); // package name
-            assertInteger(parts[2]); // uid
-            assertNotNull(parts[3]); // process
-            statesStartIndex = 4;
-        } else {
-            assertTrue(parts.length >= 5);
-            assertNotNull(parts[1]); // package name
-            assertInteger(parts[2]); // uid
-            assertInteger(parts[3]); // app version
-            assertNotNull(parts[4]); // process
-            statesStartIndex = 5;
-        }
+        assertTrue(parts.length >= 5);
+        assertNotNull(parts[1]); // package name
+        assertNonNegativeInteger(parts[2]); // uid
+        assertNonNegativeInteger(parts[3]); // app version
+        assertNotNull(parts[4]); // process
+        statesStartIndex = 5;
 
         for (int i = statesStartIndex; i < parts.length; i++) {
             String[] subparts = parts[i].split(":");
             assertEquals(2, subparts.length);
             checkTag(subparts[0], true); // tag
-            assertInteger(subparts[1]); // duration (msec)
+            assertNonNegativeInteger(subparts[1]); // duration (msec)
         }
     }
 
@@ -170,147 +183,107 @@
 
         if (hasProcess) {
             char p = tag.charAt(2);
-            assertTrue("malformed tag: " + tag, p >= 'a' && p <= 'z');
+            assertTrue("malformed tag: " + tag, "ptfbuwsxrhlace".indexOf(p) >= 0);
         }
     }
 
-    private void checkPkgPss(String[] parts, int version) {
+    private void checkPkgPss(String[] parts, boolean checkAvg) {
         int statesStartIndex;
 
-        if (version < 4) {
-            assertTrue(parts.length >= 4);
-            assertNotNull(parts[1]); // package name
-            assertInteger(parts[2]); // uid
-            assertNotNull(parts[3]); // process
-            statesStartIndex = 4;
-        } else {
-            assertTrue(parts.length >= 5);
-            assertNotNull(parts[1]); // package name
-            assertInteger(parts[2]); // uid
-            assertInteger(parts[3]); // app version
-            assertNotNull(parts[4]); // process
-            statesStartIndex = 5;
-        }
+        assertTrue(parts.length >= 5);
+        assertNotNull(parts[1]); // package name
+        assertNonNegativeInteger(parts[2]); // uid
+        assertNonNegativeInteger(parts[3]); // app version
+        assertNotNull(parts[4]); // process
+        statesStartIndex = 5;
 
         for (int i = statesStartIndex; i < parts.length; i++) {
             String[] subparts = parts[i].split(":");
             assertEquals(8, subparts.length);
             checkTag(subparts[0], true); // tag
-            assertInteger(subparts[1]); // sample size
-            assertInteger(subparts[2]); // pss min
-            assertInteger(subparts[3]); // pss avg
-            assertInteger(subparts[4]); // pss max
-            assertInteger(subparts[5]); // uss min
-            assertInteger(subparts[6]); // uss avg
-            assertInteger(subparts[7]); // uss max
+            assertNonNegativeInteger(subparts[1]); // sample size
+            assertMinAvgMax(subparts[2], subparts[3], subparts[4], checkAvg); // pss
+            assertMinAvgMax(subparts[5], subparts[6], subparts[7], checkAvg); // uss
         }
     }
 
-    private void checkPkgSvc(String[] parts, int version) {
+    private void checkPkgSvc(String[] parts) {
         int statesStartIndex;
 
-        if (version < 4) {
-            assertTrue(parts.length >= 5);
-            assertNotNull(parts[1]); // package name
-            assertInteger(parts[2]); // uid
-            assertNotNull(parts[3]); // service name
-            assertInteger(parts[4]); // count
-            statesStartIndex = 5;
-        } else {
-            assertTrue(parts.length >= 6);
-            assertNotNull(parts[1]); // package name
-            assertInteger(parts[2]); // uid
-            assertInteger(parts[3]); // app version
-            assertNotNull(parts[4]); // service name
-            assertInteger(parts[5]); // count
-            statesStartIndex = 6;
-        }
+        assertTrue(parts.length >= 6);
+        assertNotNull(parts[1]); // package name
+        assertNonNegativeInteger(parts[2]); // uid
+        assertNonNegativeInteger(parts[3]); // app version
+        assertNotNull(parts[4]); // service name
+        assertNonNegativeInteger(parts[5]); // count
+        statesStartIndex = 6;
 
         for (int i = statesStartIndex; i < parts.length; i++) {
             String[] subparts = parts[i].split(":");
             assertEquals(2, subparts.length);
             checkTag(subparts[0], false); // tag
-            assertInteger(subparts[1]); // duration (msec)
+            assertNonNegativeInteger(subparts[1]); // duration (msec)
         }
     }
 
-    private void checkPkgKills(String[] parts, int version) {
+    private void checkPkgKills(String[] parts, boolean checkAvg) {
         String pssStr;
 
-        if (version < 4) {
-            assertEquals(8, parts.length);
-            assertNotNull(parts[1]); // package name
-            assertInteger(parts[2]); // uid
-            assertNotNull(parts[3]); // process
-            assertInteger(parts[4]); // wakes
-            assertInteger(parts[5]); // cpu
-            assertInteger(parts[6]); // cached
-            pssStr = parts[7];
-        } else {
-            assertEquals(9, parts.length);
-            assertNotNull(parts[1]); // package name
-            assertInteger(parts[2]); // uid
-            assertInteger(parts[3]); // app version
-            assertNotNull(parts[4]); // process
-            assertInteger(parts[5]); // wakes
-            assertInteger(parts[6]); // cpu
-            assertInteger(parts[7]); // cached
-            pssStr = parts[8];
-        }
+        assertEquals(9, parts.length);
+        assertNotNull(parts[1]); // package name
+        assertNonNegativeInteger(parts[2]); // uid
+        assertNonNegativeInteger(parts[3]); // app version
+        assertNotNull(parts[4]); // process
+        assertNonNegativeInteger(parts[5]); // wakes
+        assertNonNegativeInteger(parts[6]); // cpu
+        assertNonNegativeInteger(parts[7]); // cached
+        pssStr = parts[8];
 
         String[] subparts = pssStr.split(":");
         assertEquals(3, subparts.length);
-        assertInteger(subparts[0]); // pss min
-        assertInteger(subparts[1]); // pss avg
-        assertInteger(subparts[2]); // pss max
+        assertMinAvgMax(subparts[0], subparts[1], subparts[2], checkAvg); // pss
     }
 
     private void checkProc(String[] parts) {
         assertTrue(parts.length >= 3);
         assertNotNull(parts[1]); // package name
-        assertInteger(parts[2]); // uid
+        assertNonNegativeInteger(parts[2]); // uid
 
         for (int i = 3; i < parts.length; i++) {
             String[] subparts = parts[i].split(":");
             assertEquals(2, subparts.length);
             checkTag(subparts[0], true); // tag
-            assertInteger(subparts[1]); // duration (msec)
+            assertNonNegativeInteger(subparts[1]); // duration (msec)
         }
     }
 
-    private void checkPss(String[] parts) {
+    private void checkPss(String[] parts, boolean checkAvg) {
         assertTrue(parts.length >= 3);
         assertNotNull(parts[1]); // package name
-        assertInteger(parts[2]); // uid
+        assertNonNegativeInteger(parts[2]); // uid
 
         for (int i = 3; i < parts.length; i++) {
             String[] subparts = parts[i].split(":");
             assertEquals(8, subparts.length);
             checkTag(subparts[0], true); // tag
-            assertInteger(subparts[1]); // sample size
-            assertInteger(subparts[2]); // pss min
-            assertInteger(subparts[3]); // pss avg
-            assertInteger(subparts[4]); // pss max
-            assertInteger(subparts[5]); // uss min
-            assertInteger(subparts[6]); // uss avg
-            assertInteger(subparts[7]); // uss max
+            assertNonNegativeInteger(subparts[1]); // sample size
+            assertMinAvgMax(subparts[2], subparts[3], subparts[4], checkAvg); // pss
+            assertMinAvgMax(subparts[5], subparts[6], subparts[7], checkAvg); // uss
         }
     }
 
-    private void checkKills(String[] parts) {
+    private void checkKills(String[] parts, boolean checkAvg) {
         assertEquals(7, parts.length);
         assertNotNull(parts[1]); // package name
-        assertInteger(parts[2]); // uid
-        assertInteger(parts[3]); // wakes
-        assertInteger(parts[4]); // cpu
-        assertInteger(parts[5]); // cached
+        assertNonNegativeInteger(parts[2]); // uid
+        assertNonNegativeInteger(parts[3]); // wakes
+        assertNonNegativeInteger(parts[4]); // cpu
+        assertNonNegativeInteger(parts[5]); // cached
         String pssStr = parts[6];
 
         String[] subparts = pssStr.split(":");
         assertEquals(3, subparts.length);
-        assertInteger(subparts[0]); // pss min
-        assertInteger(subparts[1]); // pss avg
-        assertInteger(subparts[2]); // pss max
+        assertMinAvgMax(subparts[0], subparts[1], subparts[2], checkAvg); // pss
     }
 
     private void checkTotal(String[] parts) {
@@ -319,11 +292,32 @@
             String[] subparts = parts[i].split(":");
             checkTag(subparts[0], false); // tag
 
-            if (subparts[1].contains("sysmemusage")) {
-                break; // see b/18340771
-            }
-            assertInteger(subparts[1]); // duration (msec)
+            assertNonNegativeInteger(subparts[1]); // duration (msec)
         }
     }
 
+    private void checkWithProcStatsApp() throws Exception {
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        getDevice().uninstallPackage(DEVICE_SIDE_HELPER_PACKAGE);
+        installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+
+        installPackage(DEVICE_SIDE_HELPER_APK, /* grantPermissions= */ true);
+
+        final int helperAppUid = Integer.parseInt(execCommandAndGetFirstGroup(
+                "dumpsys package " + DEVICE_SIDE_HELPER_PACKAGE, "userId=(\\d+)"));
+        CLog.i("Helper app UID: " + helperAppUid);
+
+        try {
+            // Run the device side test which makes some network requests.
+            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+                    "com.android.server.cts.procstats.ProcStatsTest", "testLaunchApp");
+        } finally {
+            if (UNINSTALL_APPS_AFTER_TESTS) {
+                getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+                getDevice().uninstallPackage(DEVICE_SIDE_HELPER_PACKAGE);
+            }
+        }
+
+        // TODO Check all the lines related to the helper.
+    }
 }
diff --git a/hostsidetests/incident/apps/batterystatsapp/Android.mk b/hostsidetests/incident/apps/batterystatsapp/Android.mk
new file mode 100644
index 0000000..b15c0b7
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/Android.mk
@@ -0,0 +1,39 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsBatteryStatsApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+    android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
new file mode 100644
index 0000000..36fe872
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.server.cts.device.batterystats" >
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".SimpleForegroundService" android:exported="true" />
+        <activity android:name=".SimpleActivity" android:label="BatteryStats Test Activity" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.server.cts.device.batterystats" />
+</manifest>
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsDeviceTestBase.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsDeviceTestBase.java
new file mode 100644
index 0000000..e53c213
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsDeviceTestBase.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * Used by BatteryStatsValidationTest.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsDeviceTestBase {
+    private static final String TAG = "BatteryStatsDeviceTest";
+
+    protected Context mContext;
+    protected PowerManager mPowerManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+    }
+}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsProcessStateTests.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsProcessStateTests.java
new file mode 100644
index 0000000..c619166
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsProcessStateTests.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Used by BatteryStatsValidationTest.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsProcessStateTests extends BatteryStatsDeviceTestBase {
+    private static final String TAG = "BatteryStatsWakeLockTests";
+
+    @Test
+    public void testForegroundService() throws Exception {
+        Intent intent = new Intent();
+        intent.setClass(mContext, SimpleForegroundService.class);
+        Notification notification = new Notification.Builder(mContext, "Foreground Service")
+                .setContentTitle("CTS Foreground")
+                .setSmallIcon(android.R.drawable.ic_secure)
+                .build();
+        mContext.getSystemService(NotificationManager.class).startServiceInForeground(intent,
+                1, notification);
+        Thread.sleep(3000);
+    }
+
+    @Test
+    public void testActivity() throws Exception {
+        Intent intent = new Intent();
+        intent.setClass(mContext, SimpleActivity.class);
+        mContext.startActivity(intent);
+    }
+}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsWakeLockTests.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsWakeLockTests.java
new file mode 100644
index 0000000..9956e79
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsWakeLockTests.java
@@ -0,0 +1,50 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Used by BatteryStatsValidationTest.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsWakeLockTests extends BatteryStatsDeviceTestBase {
+    private static final String TAG = "BatteryStatsWakeLockTests";
+
+    @Test
+    public void testHoldShortWakeLock() throws Exception {
+        PowerManager.WakeLock wl = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                "BSShortWakeLock");
+        wl.acquire();
+        Thread.sleep(500);
+        wl.release();
+    }
+
+    @Test
+    public void testHoldLongWakeLock() throws Exception {
+        PowerManager.WakeLock wl = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                "BSLongWakeLock");
+        wl.acquire();
+        Thread.sleep(3000);
+        wl.release();
+    }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleActivity.java
similarity index 61%
copy from hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
copy to hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleActivity.java
index 4e3fc89..3f5bc20 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,18 +11,17 @@
  * 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
+ * limitations under the License.
  */
 
-package android.server.cts;
+package com.android.server.cts.device.batterystats;
 
 import android.app.Activity;
-import android.graphics.Rect;
+import android.os.Bundle;
 
-public class LaunchTapToFinishPipActivity extends Activity {
+public class SimpleActivity extends Activity{
     @Override
-    protected void onResume() {
-        super.onResume();
-        PipActivity.launchActivity(this, new Rect(0, 0, 500, 500), true /* tapToLaunch */);
+    public void onCreate(Bundle bundle) {
+        finish();
     }
 }
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleForegroundService.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleForegroundService.java
new file mode 100644
index 0000000..b8d4507
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleForegroundService.java
@@ -0,0 +1,81 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+
+public class SimpleForegroundService extends Service {
+
+    private Looper mServiceLooper;
+    private ServiceHandler mServiceHandler;
+
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            // Sleep for 2 seconds.
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException e) {
+                // Restore interrupt status.
+                Thread.currentThread().interrupt();
+            }
+            // Stop the service using the startId, so that we don't stop
+            // the service in the middle of handling another job
+            stopSelf(msg.arg1);
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        // Start up the thread running the service.  Note that we create a
+        // separate thread because the service normally runs in the process's
+        // main thread, which we don't want to block.  We also make it
+        // background priority so CPU-intensive work will not disrupt our UI.
+        HandlerThread thread = new HandlerThread("ServiceStartArguments",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+
+        // Get the HandlerThread's Looper and use it for our Handler
+        mServiceLooper = thread.getLooper();
+        mServiceHandler = new ServiceHandler(mServiceLooper);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Message msg = mServiceHandler.obtainMessage();
+        msg.arg1 = startId;
+        mServiceHandler.sendMessage(msg);
+
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/hostsidetests/incident/apps/boundwidgetapp/Android.mk b/hostsidetests/incident/apps/boundwidgetapp/Android.mk
new file mode 100644
index 0000000..e9c1555
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/Android.mk
@@ -0,0 +1,39 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppWidgetApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+    android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml b/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
new file mode 100644
index 0000000..312d05d
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="android.appwidget.cts">
+
+    <application>
+
+      <uses-library android:name="android.test.runner"/>
+
+      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider" >
+          <intent-filter>
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+          </intent-filter>
+          <meta-data android:name="android.appwidget.provider"
+              android:resource="@xml/appwidget_info" />
+      </receiver>
+
+      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider" >
+          <intent-filter>
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+          </intent-filter>
+          <meta-data android:name="android.appwidget.provider"
+              android:resource="@xml/appwidget_info" />
+      </receiver>
+
+  </application>
+
+  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+      android:targetPackage="android.appwidget.cts"
+      android:label="CTS Tests for the dumpsys protobuf protocol">
+      <meta-data android:name="listener"
+          android:value="com.android.cts.runner.CtsTestRunListener" />
+  </instrumentation>
+</manifest>
diff --git a/hostsidetests/incident/apps/boundwidgetapp/res/xml/appwidget_info.xml b/hostsidetests/incident/apps/boundwidgetapp/res/xml/appwidget_info.xml
new file mode 100644
index 0000000..3d5d0f8c
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/res/xml/appwidget_info.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+     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.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="40dp"
+    android:minHeight="40dp"
+    android:minResizeWidth="60dp"
+    android:minResizeHeight="60dp"
+    android:updatePeriodMillis="86400000"
+    android:resizeMode="horizontal|vertical">
+</appwidget-provider>
diff --git a/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/AppWidgetTest.java b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/AppWidgetTest.java
new file mode 100644
index 0000000..baf2ed3
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/AppWidgetTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.appwidget.cts;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.cts.provider.FirstAppWidgetProvider;
+import android.appwidget.cts.provider.SecondAppWidgetProvider;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.test.InstrumentationTestCase;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+public class AppWidgetTest extends InstrumentationTestCase {
+
+    private static final String TAG = "AppWidgetTest";
+    private static final int HOST_ID = 1001;
+    private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND =
+        "appwidget grantbind --package android.appwidget.cts --user 0";
+
+    private Context mContext;
+
+    @Override
+    public void setUp() throws Exception {
+        mContext = getInstrumentation().getTargetContext();
+        // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
+        // Dexmaker is used by mockito.
+        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath());
+    }
+
+    public void testBindWidget1() throws Exception {
+        Log.d(TAG, "binding widget 1 ...");
+        setupWidget(getFirstAppWidgetProviderInfo(), null, true);
+        Log.d(TAG, "binding widget 1 done");
+    }
+
+    public void testBindWidget2() throws Exception {
+        Log.d(TAG, "binding widget 2 ...");
+        Bundle options = new Bundle();
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 1);
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 2);
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 3);
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 4);
+        setupWidget(getSecondAppWidgetProviderInfo(), options, true);
+        Log.d(TAG, "binding widget 2 done");
+    }
+
+    public void testAllocateOnlyWidget1() throws Exception {
+        Log.d(TAG, "allocating widget 1 ...");
+        setupWidget(getFirstAppWidgetProviderInfo(), null, false);
+        Log.d(TAG, "allocating widget 1 done");
+    }
+
+    public void testCleanup() throws Exception {
+        Log.d(TAG, "deleting host");
+        grantBindAppWidgetPermission();
+        AppWidgetHost host = new AppWidgetHost(mContext, HOST_ID);
+        host.deleteHost();
+    }
+
+    private void setupWidget(AppWidgetProviderInfo prov, Bundle options, boolean bindWidget) throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(mContext, HOST_ID);
+        host.deleteHost();
+        host.startListening();
+
+        AppWidgetManager mgr = getAppWidgetManager();
+
+        // Initially we have no widgets.
+        assertEquals(0, mgr.getAppWidgetIds(prov.provider).length);
+
+        // Allocate the first widget id to bind.
+        int appWidgetId = host.allocateAppWidgetId();
+
+        if (bindWidget) {
+            // Bind the widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(appWidgetId,
+                prov.getProfile(), prov.provider, options);
+
+            assertEquals(1, mgr.getAppWidgetIds(prov.provider).length);
+        }
+    }
+
+    private ComponentName getFirstWidgetComponent() {
+        return new ComponentName(mContext.getPackageName(),
+                FirstAppWidgetProvider.class.getName());
+    }
+
+    private ComponentName getSecondWidgetComponent() {
+        return new ComponentName(mContext.getPackageName(),
+                SecondAppWidgetProvider.class.getName());
+    }
+
+    private ArrayList<String> runShellCommand(String command) throws Exception {
+        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+                .executeShellCommand(command);
+
+        ArrayList<String> ret = new ArrayList<>();
+        // Read the input stream fully.
+        try (BufferedReader r = new BufferedReader(
+                new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(pfd)))) {
+            String line;
+            while ((line = r.readLine()) != null) {
+                ret.add(line);
+            }
+        }
+        return ret;
+    }
+
+    private AppWidgetProviderInfo getFirstAppWidgetProviderInfo() {
+        return getProviderInfo(getFirstWidgetComponent());
+    }
+
+    private AppWidgetProviderInfo getSecondAppWidgetProviderInfo() {
+        return getProviderInfo(getSecondWidgetComponent());
+    }
+
+    private AppWidgetProviderInfo getProviderInfo(ComponentName componentName) {
+        List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
+
+        final int providerCount = providers.size();
+        for (int i = 0; i < providerCount; i++) {
+            AppWidgetProviderInfo provider = providers.get(i);
+            if (componentName.equals(provider.provider)
+                && Process.myUserHandle().equals(provider.getProfile())) {
+                return provider;
+
+            }
+        }
+
+        return null;
+    }
+
+    private AppWidgetManager getAppWidgetManager() {
+        return (AppWidgetManager) getInstrumentation().getTargetContext()
+            .getSystemService(Context.APPWIDGET_SERVICE);
+    }
+
+    private void grantBindAppWidgetPermission() throws Exception {
+        runShellCommand(GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND);
+    }
+
+}
diff --git a/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/FirstAppWidgetProvider.java b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/FirstAppWidgetProvider.java
new file mode 100644
index 0000000..e3ffa8f
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/FirstAppWidgetProvider.java
@@ -0,0 +1,28 @@
+/*
+ * 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.appwidget.cts.provider;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public final class FirstAppWidgetProvider extends BroadcastReceiver {
+
+    public void onReceive(Context context, Intent data) {
+    }
+
+}
diff --git a/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/SecondAppWidgetProvider.java b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/SecondAppWidgetProvider.java
new file mode 100644
index 0000000..c7dc1c9
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/SecondAppWidgetProvider.java
@@ -0,0 +1,28 @@
+/*
+ * 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.appwidget.cts.provider;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public final class SecondAppWidgetProvider extends BroadcastReceiver {
+
+    public void onReceive(Context context, Intent data) {
+    }
+
+}
diff --git a/hostsidetests/incident/apps/netstatsapp/src/com/android/server/cts/netstats/NetstatsDeviceTest.java b/hostsidetests/incident/apps/netstatsapp/src/com/android/server/cts/netstats/NetstatsDeviceTest.java
index 075a393..d0af01b 100644
--- a/hostsidetests/incident/apps/netstatsapp/src/com/android/server/cts/netstats/NetstatsDeviceTest.java
+++ b/hostsidetests/incident/apps/netstatsapp/src/com/android/server/cts/netstats/NetstatsDeviceTest.java
@@ -15,8 +15,7 @@
  */
 package com.android.server.cts.netstats;
 
-import static junit.framework.Assert.assertEquals;
-
+import android.net.TrafficStats;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
@@ -34,12 +33,27 @@
 public class NetstatsDeviceTest {
     private static final String TAG = "NetstatsDeviceTest";
 
-    @Test
-    public void testDoNetwork() throws Exception {
-        Log.i(TAG, "Making network requests...");
+    private static final int NET_TAG = 123123123;
 
+    @Test
+    public void testDoNetworkWithoutTagging() throws Exception {
+        Log.i(TAG, "testDoNetworkWithoutTagging");
+
+        makeNetworkRequest();
+    }
+
+    @Test
+    public void testDoNetworkWithTagging() throws Exception {
+        Log.i(TAG, "testDoNetworkWithTagging");
+
+        TrafficStats.getAndSetThreadStatsTag(NET_TAG);
+        makeNetworkRequest();
+    }
+
+    private void makeNetworkRequest() throws Exception {
         final URL url = new URL("http://www.android.com/");
         final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+        HttpURLConnection.setFollowRedirects(true);
         try {
             final int status = urlConnection.getResponseCode();
 
diff --git a/hostsidetests/incident/src/com/android/server/cts/AppWidgetIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AppWidgetIncidentTest.java
new file mode 100644
index 0000000..9ffe5c4
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/AppWidgetIncidentTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.server.cts;
+
+import android.service.appwidget.AppWidgetServiceDumpProto;
+import android.service.appwidget.WidgetProto;
+
+/**
+ * Test to check that the appwidget service properly outputs its dump state.
+ */
+public class AppWidgetIncidentTest extends ProtoDumpTestCase {
+
+    private static final String DEVICE_TEST_CLASS = ".AppWidgetTest";
+    private static final String DEVICE_SIDE_TEST_APK = "CtsAppWidgetApp.apk";
+    private static final String DEVICE_SIDE_TEST_PACKAGE = "android.appwidget.cts";
+    private static final String DEVICE_SIDE_WIDGET_CLASS_1 =
+        "android.appwidget.cts.provider.FirstAppWidgetProvider";
+    private static final String DEVICE_SIDE_WIDGET_CLASS_2 =
+        "android.appwidget.cts.provider.SecondAppWidgetProvider";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    private boolean hasAppWidgets() throws Exception {
+        return getDevice().hasFeature("android.software.app_widgets");
+    }
+
+    public void testAppWidgetProtoDump_firstComponent() throws Exception {
+        if (!hasAppWidgets()) {
+            return;
+        }
+
+        WidgetProto widget = prepare("testBindWidget1");
+
+        assertNotNull(widget);
+        assertEquals(DEVICE_SIDE_TEST_PACKAGE,
+            widget.getProviderPackage());
+        assertEquals(DEVICE_SIDE_WIDGET_CLASS_1,
+            widget.getProviderClass());
+        assertEquals(false, widget.getIsCrossProfile());
+        assertEquals(false, widget.getIsHostStopped());
+        assertEquals(0, widget.getMinWidth());
+        assertEquals(0, widget.getMinHeight());
+        assertEquals(0, widget.getMaxWidth());
+        assertEquals(0, widget.getMaxHeight());
+
+        cleanup();
+    }
+
+    public void testAppWidgetProtoDump_secondComponent() throws Exception {
+        if (!hasAppWidgets()) {
+            return;
+        }
+
+        WidgetProto widget = prepare("testBindWidget2");
+
+        assertNotNull(widget);
+        assertEquals(DEVICE_SIDE_TEST_PACKAGE,
+            widget.getProviderPackage());
+        assertEquals(DEVICE_SIDE_WIDGET_CLASS_2,
+            widget.getProviderClass());
+        assertEquals(false, widget.getIsCrossProfile());
+        assertEquals(false, widget.getIsHostStopped());
+        assertEquals(1, widget.getMinWidth());
+        assertEquals(2, widget.getMinHeight());
+        assertEquals(3, widget.getMaxWidth());
+        assertEquals(4, widget.getMaxHeight());
+
+        cleanup();
+    }
+
+    public void testAppWidgetProtoDump_firstComponentNotBound() throws Exception {
+        if (!hasAppWidgets()) {
+            return;
+        }
+
+        WidgetProto widget = prepare("testAllocateOnlyWidget1");
+
+        // a widget that is not bound must not show up in the dump
+        assertNull(widget);
+
+        cleanup();
+    }
+
+    private WidgetProto prepare(String testMethodName) throws Exception {
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, DEVICE_TEST_CLASS, testMethodName);
+
+        AppWidgetServiceDumpProto dump = getDump(AppWidgetServiceDumpProto.parser(),
+            "dumpsys appwidget --proto");
+
+        for (WidgetProto widgetProto : dump.getWidgetsList()) {
+            if (DEVICE_SIDE_TEST_PACKAGE.equals(widgetProto.getHostPackage())) {
+                return widgetProto;
+            }
+        }
+        return null;
+    }
+
+    private void cleanup() throws Exception {
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, DEVICE_TEST_CLASS, "testCleanup");
+    }
+
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
new file mode 100644
index 0000000..ccd301a
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.server.cts;
+
+import android.service.battery.BatteryServiceDumpProto;
+
+import java.util.Scanner;
+
+/**
+ * Test to check that the battery manager properly outputs its dump state.
+ */
+public class BatteryIncidentTest extends ProtoDumpTestCase {
+    public void testBatteryServiceDump() throws Exception {
+        final BatteryServiceDumpProto dump = getDump(BatteryServiceDumpProto.parser(),
+                                                     "dumpsys battery --proto");
+
+        assertTrue(dump.getPlugged()!=BatteryServiceDumpProto.BatteryPlugged.BATTERY_PLUGGED_WIRELESS);
+        assertTrue(dump.getMaxChargingCurrent() > 0);
+        assertTrue(dump.getMaxChargingVoltage() > 0);
+        assertTrue(dump.getChargeCounter() > 0);
+        assertTrue(dump.getStatus()!=BatteryServiceDumpProto.BatteryStatus.BATTERY_STATUS_INVALID);
+        assertTrue(dump.getHealth()!=BatteryServiceDumpProto.BatteryHealth.BATTERY_HEALTH_INVALID);
+        int scale = dump.getScale();
+        assertTrue(scale > 0);
+        int level = dump.getLevel();
+        assertTrue(level >= 0 && level <= scale);
+        assertTrue(dump.getVoltage() > 0);
+        assertTrue(dump.getTemperature() > 0);
+        assertNotNull(dump.getTechnology());
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
new file mode 100644
index 0000000..08c79bd
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.server.cts;
+
+
+import com.android.tradefed.log.LogUtil;
+
+/**
+ * Test for "dumpsys batterystats -c
+ *
+ * Validates reporting of battery stats based on different events
+ */
+public class BatteryStatsValidationTest extends ProtoDumpTestCase {
+    private static final String TAG = "BatteryStatsValidationTest";
+
+    private static final String DEVICE_SIDE_TEST_APK = "CtsBatteryStatsApp.apk";
+    private static final String DEVICE_SIDE_TEST_PACKAGE
+            = "com.android.server.cts.device.batterystats";
+
+    @Override
+    protected void tearDown() throws Exception {
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+
+        batteryOffScreenOn();
+        super.tearDown();
+    }
+
+    protected void batteryOnScreenOff() throws Exception {
+        getDevice().executeShellCommand("dumpsys battery unplug");
+        getDevice().executeShellCommand("dumpsys batterystats enable pretend-screen-off");
+    }
+
+    protected void batteryOffScreenOn() throws Exception {
+        getDevice().executeShellCommand("dumpsys battery reset");
+        getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
+    }
+
+    public void testWakeLockDuration() throws Exception {
+        batteryOnScreenOff();
+
+        installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
+                "testHoldShortWakeLock");
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
+                "testHoldLongWakeLock");
+
+        assertValueRange("wl", "BSShortWakeLock", 14, (long) (500 * 0.9), 500 * 2);
+        assertValueRange("wl", "BSLongWakeLock", 14, (long) (3000 * 0.9), 3000 * 2);
+
+        batteryOffScreenOn();
+    }
+
+    public void testServiceForegroundDuration() throws Exception {
+        batteryOnScreenOff();
+        installPackage(DEVICE_SIDE_TEST_APK, true);
+
+        getDevice().executeShellCommand(
+                "am start -n com.android.server.cts.device.batterystats/.SimpleActivity");
+        assertValueRange("st", "", 5, 0, 0); // No foreground service time before test
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsProcessStateTests",
+                "testForegroundService");
+        assertValueRange("st", "", 5, (long) (2000 * 0.8), 4000);
+
+        batteryOffScreenOn();
+    }
+
+    /**
+     * Verifies that the recorded time for the specified tag and name in the test package
+     * is within the specified range
+     * @throws Exception
+     */
+    private void assertValueRange(String tag, String optionalAfterTag,
+            int index, long min, long max) throws Exception {
+        String dumpsys = getDevice().executeShellCommand("dumpsys batterystats --checkin");
+        String uidLine = getDevice().executeShellCommand("cmd package list packages -U "
+                + DEVICE_SIDE_TEST_PACKAGE);
+        String[] uidLineParts = uidLine.split(":");
+        // 3rd entry is package uid
+        assertTrue(uidLineParts.length > 2);
+        int uid = Integer.parseInt(uidLineParts[2].trim());
+        assertTrue(uid > 10000);
+
+        String[] lines = dumpsys.split("\n");
+        long time = 0;
+        for (int i = lines.length - 1; i >= 0; i--) {
+            String line = lines[i];
+            if (line.contains(uid + ",l," + tag + "," + optionalAfterTag)) {
+                String[] wlParts = line.split(",");
+                //System.err.println(line);
+                time = Long.parseLong(wlParts[index]);
+            }
+        }
+        assertTrue("Value not greater than min", time >= min);
+        assertTrue("Value not less than max", time <= max);
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
index 48fcca0..397e598 100644
--- a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
@@ -24,7 +24,11 @@
 import android.service.NetworkStatsRecorderProto;
 import android.service.NetworkStatsServiceDumpProto;
 
+import com.android.tradefed.log.LogUtil.CLog;
+
 import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
 /**
  * Test for "dumpsys netstats --proto"
@@ -50,32 +54,6 @@
         super.tearDown();
     }
 
-    /**
-     * Parse the output of "dumpsys netstats --proto" and make sure all the values are probable.
-     */
-    public void testSanityCheck() throws Exception {
-        installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
-
-        // Run the device side test which makes some network requests.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, null, null);
-
-        // Also does ping for more network activity.
-        getDevice().executeShellCommand("ping -c 8 -i 0 8.8.8.8");
-
-        // Force refresh the output.
-        getDevice().executeShellCommand("dumpsys netstats --poll");
-
-        final NetworkStatsServiceDumpProto dump = getDump(NetworkStatsServiceDumpProto.parser(),
-                "dumpsys netstats --proto");
-
-        checkInterfaces(dump.getActiveInterfacesList());
-        checkInterfaces(dump.getActiveUidInterfacesList());
-
-        checkStats(dump.getDevStats(), /*withUid=*/ false, /*withTag=*/ false);
-        checkStats(dump.getXtStats(), /*withUid=*/ false, /*withTag=*/ false);
-        checkStats(dump.getUidStats(), /*withUid=*/ true, /*withTag=*/ false);
-        checkStats(dump.getUidTagStats(), /*withUid=*/ true, /*withTag=*/ true);
-    }
 
     private void assertPositive(String name, long value) {
         if (value > 0) return;
@@ -87,6 +65,191 @@
         fail(name + " expected to be zero or positive, but was: " + value);
     }
 
+    private void assertGreaterOrEqual(long greater, long lesser) {
+        assertTrue("" + greater + " expected to be greater than or equal to " + lesser,
+                greater >= lesser);
+    }
+
+    /**
+     * Parse the output of "dumpsys netstats --proto" and make sure all the values are probable.
+     */
+    public void testSanityCheck() throws Exception {
+
+        final long st = System.currentTimeMillis();
+
+        installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+
+        // Find the package UID.
+        final int uid = Integer.parseInt(execCommandAndGetFirstGroup(
+                "dumpsys package " + DEVICE_SIDE_TEST_PACKAGE, "userId=(\\d+)"));
+
+        CLog.i("Start time: " + st);
+        CLog.i("App UID: " + uid);
+
+        // Run the device side test which makes some network requests.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, null, null);
+
+        // Make some more activity.
+        getDevice().executeShellCommand("ping -s 100 -c 10 -i 0  www.android.com");
+
+        // Force refresh the output.
+        getDevice().executeShellCommand("dumpsys netstats --poll");
+
+        NetworkStatsServiceDumpProto dump = getDump(NetworkStatsServiceDumpProto.parser(),
+                "dumpsys netstats --proto");
+
+        CLog.d("First dump:\n" + dump.toString());
+
+        // Basic sanity check.
+        checkInterfaces(dump.getActiveInterfacesList());
+        checkInterfaces(dump.getActiveUidInterfacesList());
+
+        checkStats(dump.getDevStats(), /*withUid=*/ false, /*withTag=*/ false);
+        checkStats(dump.getXtStats(), /*withUid=*/ false, /*withTag=*/ false);
+        checkStats(dump.getUidStats(), /*withUid=*/ true, /*withTag=*/ false);
+        checkStats(dump.getUidTagStats(), /*withUid=*/ true, /*withTag=*/ true);
+
+        // Remember the original values.
+        final Predicate<NetworkStatsCollectionKeyProto> uidFilt = key -> key.getUid() == uid;
+        final Predicate<NetworkStatsCollectionKeyProto> tagFilt =
+                key -> (key.getTag() == 123123123) && (key.getUid() == uid);
+
+        final long devRxPackets = sum(dump.getDevStats(), st, b -> b.getRxPackets());
+        final long devRxBytes = sum(dump.getDevStats(), st, b -> b.getRxBytes());
+        final long devTxPackets = sum(dump.getDevStats(), st, b -> b.getTxPackets());
+        final long devTxBytes = sum(dump.getDevStats(), st, b -> b.getTxBytes());
+
+        final long xtRxPackets = sum(dump.getXtStats(), st, b -> b.getRxPackets());
+        final long xtRxBytes = sum(dump.getXtStats(), st, b -> b.getRxBytes());
+        final long xtTxPackets = sum(dump.getXtStats(), st, b -> b.getTxPackets());
+        final long xtTxBytes = sum(dump.getXtStats(), st, b -> b.getTxBytes());
+
+        final long uidRxPackets = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxPackets());
+        final long uidRxBytes = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxBytes());
+        final long uidTxPackets = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxPackets());
+        final long uidTxBytes = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxBytes());
+
+        final long tagRxPackets = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxPackets());
+        final long tagRxBytes = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxBytes());
+        final long tagTxPackets = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxPackets());
+        final long tagTxBytes = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxBytes());
+
+        // Run again to make some more activity.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+                "com.android.server.cts.netstats.NetstatsDeviceTest",
+                "testDoNetworkWithoutTagging");
+
+        getDevice().executeShellCommand("dumpsys netstats --poll");
+        dump = getDump(NetworkStatsServiceDumpProto.parser(), "dumpsys netstats --proto");
+
+        CLog.d("Second dump:\n" + dump.toString());
+
+        final long devRxPackets2 = sum(dump.getDevStats(), st, b -> b.getRxPackets());
+        final long devRxBytes2 = sum(dump.getDevStats(), st, b -> b.getRxBytes());
+        final long devTxPackets2 = sum(dump.getDevStats(), st, b -> b.getTxPackets());
+        final long devTxBytes2 = sum(dump.getDevStats(), st, b -> b.getTxBytes());
+
+        final long xtRxPackets2 = sum(dump.getXtStats(), st, b -> b.getRxPackets());
+        final long xtRxBytes2 = sum(dump.getXtStats(), st, b -> b.getRxBytes());
+        final long xtTxPackets2 = sum(dump.getXtStats(), st, b -> b.getTxPackets());
+        final long xtTxBytes2 = sum(dump.getXtStats(), st, b -> b.getTxBytes());
+
+        final long uidRxPackets2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxPackets());
+        final long uidRxBytes2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxBytes());
+        final long uidTxPackets2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxPackets());
+        final long uidTxBytes2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxBytes());
+
+        final long tagRxPackets2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxPackets());
+        final long tagRxBytes2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxBytes());
+        final long tagTxPackets2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxPackets());
+        final long tagTxBytes2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxBytes());
+
+        // At least 1 packet, 100 bytes sent.
+        assertGreaterOrEqual(uidTxPackets2, uidTxPackets + 1);
+        assertGreaterOrEqual(uidTxBytes2, uidTxBytes + 100);
+
+//        assertGreaterOrEqual(tagTxPackets2, tagTxPackets + 1);
+//        assertGreaterOrEqual(tagTxBytes2, tagTxBytes + 100);
+
+        // At least 2 packets, 100 bytes sent.
+        assertGreaterOrEqual(uidRxPackets2, uidRxPackets + 2);
+        assertGreaterOrEqual(uidRxBytes2, uidRxBytes + 100);
+
+//        assertGreaterOrEqual(tagRxPackets2, tagRxPackets + 2);
+//        assertGreaterOrEqual(tagRxBytes2, tagRxBytes + 100);
+
+        // Run again to make some more activity.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+                "com.android.server.cts.netstats.NetstatsDeviceTest",
+                "testDoNetworkWithTagging");
+
+        getDevice().executeShellCommand("dumpsys netstats --poll");
+        dump = getDump(NetworkStatsServiceDumpProto.parser(), "dumpsys netstats --proto");
+
+        CLog.d("Second dump:\n" + dump.toString());
+
+        final long devRxPackets3 = sum(dump.getDevStats(), st, b -> b.getRxPackets());
+        final long devRxBytes3 = sum(dump.getDevStats(), st, b -> b.getRxBytes());
+        final long devTxPackets3 = sum(dump.getDevStats(), st, b -> b.getTxPackets());
+        final long devTxBytes3 = sum(dump.getDevStats(), st, b -> b.getTxBytes());
+
+        final long xtRxPackets3 = sum(dump.getXtStats(), st, b -> b.getRxPackets());
+        final long xtRxBytes3 = sum(dump.getXtStats(), st, b -> b.getRxBytes());
+        final long xtTxPackets3 = sum(dump.getXtStats(), st, b -> b.getTxPackets());
+        final long xtTxBytes3 = sum(dump.getXtStats(), st, b -> b.getTxBytes());
+
+        final long uidRxPackets3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxPackets());
+        final long uidRxBytes3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxBytes());
+        final long uidTxPackets3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxPackets());
+        final long uidTxBytes3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxBytes());
+
+        final long tagRxPackets3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxPackets());
+        final long tagRxBytes3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxBytes());
+        final long tagTxPackets3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxPackets());
+        final long tagTxBytes3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxBytes());
+
+        // At least 1 packet, 100 bytes sent.
+        assertGreaterOrEqual(uidTxPackets3, uidTxPackets2 + 1);
+        assertGreaterOrEqual(uidTxBytes3, uidTxBytes2 + 100);
+
+        assertGreaterOrEqual(tagTxPackets3, tagTxPackets2 + 1);
+        assertGreaterOrEqual(tagTxBytes3, tagTxBytes2 + 100);
+
+        // At least 2 packets, 100 bytes sent.
+        assertGreaterOrEqual(uidRxPackets3, uidRxPackets2 + 2);
+        assertGreaterOrEqual(uidRxBytes3, uidRxBytes2 + 100);
+
+        assertGreaterOrEqual(tagRxPackets3, tagRxPackets2 + 2);
+        assertGreaterOrEqual(tagRxBytes3, tagRxBytes2 + 100);
+    }
+
+    private long sum(NetworkStatsRecorderProto recorder,
+            long startTime,
+            Function<NetworkStatsHistoryBucketProto, Long> func) {
+        return sum(recorder, startTime, key -> true, func);
+    }
+
+    private long sum(NetworkStatsRecorderProto recorder,
+            long startTime,
+            Predicate<NetworkStatsCollectionKeyProto> filter,
+            Function<NetworkStatsHistoryBucketProto, Long> func) {
+
+        long total = 0;
+        for (NetworkStatsCollectionStatsProto stats
+                : recorder.getCompleteHistory().getStatsList()) {
+            if (!filter.test(stats.getKey())) {
+                continue;
+            }
+            for (NetworkStatsHistoryBucketProto bucket : stats.getHistory().getBucketsList()) {
+                if (startTime < bucket.getBucketStartMs()) {
+                    continue;
+                }
+                total += func.apply(bucket);
+            }
+        }
+        return total;
+    }
+
     private void checkInterfaces(List<NetworkInterfaceProto> interfaces) {
         /* Example:
     active_interfaces=[
@@ -128,7 +291,7 @@
         assertFalse("There must be at least one non-metered interface during CTS", allMetered);
     }
 
-    private void checkStats(NetworkStatsRecorderProto record, boolean withUid, boolean withTag) {
+    private void checkStats(NetworkStatsRecorderProto recorder, boolean withUid, boolean withTag) {
         /*
          * Example:
     dev_stats=NetworkStatsRecorderProto {
@@ -173,9 +336,9 @@
                 }
          */
 
-        assertNotNegative("Pending bytes", record.getPendingTotalBytes());
+        assertNotNegative("Pending bytes", recorder.getPendingTotalBytes());
 
-        for (NetworkStatsCollectionStatsProto stats : record.getCompleteHistory().getStatsList()) {
+        for (NetworkStatsCollectionStatsProto stats : recorder.getCompleteHistory().getStatsList()) {
 
             final NetworkStatsCollectionKeyProto key = stats.getKey();
 
@@ -208,8 +371,8 @@
                 assertNotNegative("TX bytes", bucket.getTxBytes());
                 assertNotNegative("TX packets", bucket.getTxPackets());
 
-                // It should be safe to say # of bytes >= 10 * 10 of packets, due to headers, etc...
-                final long FACTOR = 10;
+// 10 was still too big?                // It should be safe to say # of bytes >= 10 * 10 of packets, due to headers, etc...
+                final long FACTOR = 4;
                 assertTrue(
                         String.format("# of bytes %d too small for # of packets %d",
                                 bucket.getRxBytes(), bucket.getRxPackets()),
diff --git a/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java b/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java
new file mode 100644
index 0000000..dac0672
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.server.cts;
+
+import android.service.notification.NotificationRecordProto;
+import android.service.notification.NotificationServiceDumpProto;
+import android.service.notification.State;
+
+/**
+ * Test to check that the notification service properly outputs its dump state.
+ */
+public class NotificationTest extends ProtoDumpTestCase {
+    /**
+     * Tests that at least one notification is posted, and verify its properties are plausible.
+     */
+    public void testNotificationRecords() throws Exception {
+        final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
+                "dumpsys notification --proto");
+
+        assertTrue(dump.getRecordsCount() > 0);
+        boolean found = false;
+        for (NotificationRecordProto record : dump.getRecordsList()) {
+            if (record.getKey().contains("android")) {
+                found = true;
+                assertEquals(State.POSTED, record.getState());
+                assertTrue(record.getImportance() > 0 /* NotificationManager.IMPORTANCE_NONE */);
+                assertEquals(record.getKey(), record.getGroupKey());
+
+                // Ensure these fields exist, at least
+                record.getFlags();
+                record.getChannelId();
+                record.getSound();
+                record.getSoundUsage();
+                record.getCanVibrate();
+                record.getCanShowLight();
+                record.getGroupKey();
+            }
+        }
+
+        assertTrue(found);
+    }
+}
+
diff --git a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
index 0ec59c2..09734bb 100644
--- a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
+++ b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
@@ -24,6 +24,7 @@
 import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.CollectingTestListener;
@@ -36,6 +37,8 @@
 
 import java.io.FileNotFoundException;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -141,4 +144,27 @@
             throw new AssertionError(errorBuilder.toString());
         }
     }
+
+    /**
+     * Execute the given command, and find the given pattern and return the resulting
+     * {@link Matcher}.
+     */
+    protected Matcher execCommandAndFind(String command, String pattern) throws Exception {
+        final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        getDevice().executeShellCommand(command, receiver);
+        final String output = receiver.getOutput();
+        final Matcher matcher = Pattern.compile(pattern).matcher(output);
+        assertTrue("Pattern '" + pattern + "' didn't match. Output=\n" + output, matcher.find());
+        return matcher;
+    }
+
+    /**
+     * Execute the given command, find the given pattern, and return the first captured group
+     * as a String.
+     */
+    protected String execCommandAndGetFirstGroup(String command, String pattern) throws Exception {
+        final Matcher matcher = execCommandAndFind(command, pattern);
+        assertTrue("No group found for pattern '" + pattern + "'", matcher.groupCount() > 0);
+        return matcher.group(1);
+    }
 }
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
index f59cba1..aa54075 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
@@ -196,7 +196,8 @@
      * Sends a system notification containing actions with pending intents to launch the app's
      * main activitiy or service.
      */
-    static void sendNotification(Context context, int notificationId, String notificationType ) {
+    static void sendNotification(Context context, String channelId, int notificationId,
+            String notificationType ) {
         Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType);
         final Intent serviceIntent = new Intent(context, MyService.class);
         final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
@@ -204,7 +205,7 @@
         final Bundle bundle = new Bundle();
         bundle.putCharSequence("parcelable", "I am not");
 
-        final Notification.Builder builder = new Notification.Builder(context)
+        final Notification.Builder builder = new Notification.Builder(context, channelId)
                 .setSmallIcon(R.drawable.ic_notification);
 
         Action action = null;
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
index b88c45d..fa3fdd1 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
@@ -18,6 +18,8 @@
 import static com.android.cts.net.hostside.app2.Common.TAG;
 import android.R;
 import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
@@ -27,7 +29,7 @@
  * Service used to change app state to FOREGROUND_SERVICE.
  */
 public class MyForegroundService extends Service {
-
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/MyForegroundService";
     private static final int FLAG_START_FOREGROUND = 1;
     private static final int FLAG_STOP_FOREGROUND = 2;
 
@@ -39,10 +41,14 @@
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         Log.v(TAG, "MyForegroundService.onStartCommand(): " + intent);
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        notificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
         switch (intent.getFlags()) {
             case FLAG_START_FOREGROUND:
                 Log.d(TAG, "Starting foreground");
-                startForeground(42, new Notification.Builder(this)
+                startForeground(42, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(R.drawable.ic_dialog_alert) // any icon is fine
                         .build());
                 break;
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyService.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 9c19e50..2496c4a 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -20,6 +20,8 @@
 import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
 import static com.android.cts.net.hostside.app2.Common.TAG;
 
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
@@ -36,6 +38,7 @@
  * Service used to dynamically register a broadcast receiver.
  */
 public class MyService extends Service {
+    private static final String NOTIFICATION_CHANNEL_ID = "MyService";
 
     private MyBroadcastReceiver mReceiver;
 
@@ -75,8 +78,8 @@
 
         @Override
         public void sendNotification(int notificationId, String notificationType) {
-            MyBroadcastReceiver
-                .sendNotification(getApplicationContext(), notificationId, notificationType);
+            MyBroadcastReceiver .sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
+                    notificationId, notificationType);
         }
       };
 
@@ -86,7 +89,18 @@
     }
 
     @Override
+    public void onCreate() {
+        final Context context = getApplicationContext();
+        ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
+                .createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                        NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
+    }
+
+    @Override
     public void onDestroy() {
+        final Context context = getApplicationContext();
+        ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
+                .deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
         if (mReceiver != null) {
             Log.d(TAG, "onDestroy(): unregistering " + mReceiver);
             getApplicationContext().unregisterReceiver(mReceiver);
diff --git a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
index 7452d22..a680123 100644
--- a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
@@ -90,7 +90,7 @@
                     STATIC_LIB_CONSUMER1_APK), false, false));
             // Try to load code and resources
             runDeviceTests(STATIC_LIB_CONSUMER1_PKG,
-                    "android.os.lib.consumer1.UseSharedLibraryTest",
+                    "android.os.lib.consumer1.CookieTest",
                     "testLoadCodeAndResources");
         } finally {
             getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
@@ -205,7 +205,7 @@
                     STATIC_LIB_CONSUMER2_APK), false, false));
             // Ensure code and resources can be loaded
             runDeviceTests(STATIC_LIB_CONSUMER2_PKG,
-                    "android.os.lib.consumer2.UseSharedLibraryTest",
+                    "android.os.lib.consumer2.CookieTest",
                     "testLoadCodeAndResources");
         } finally {
             getDevice().uninstallPackage(STATIC_LIB_CONSUMER2_PKG);
@@ -270,7 +270,7 @@
                     STATIC_LIB_CONSUMER2_APK), false, false));
             // Ensure libraries are properly reported
             runDeviceTests(STATIC_LIB_CONSUMER1_PKG,
-                    "android.os.lib.consumer1.UseSharedLibraryTest",
+                    "android.os.lib.consumer1.CookieTest",
                     "testSharedLibrariesProperlyReported");
         } finally {
             getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
@@ -297,7 +297,7 @@
                     STATIC_LIB_CONSUMER1_APK), false, false));
             // Ensure the client can see only the lib it depends on
             runDeviceTests(STATIC_LIB_CONSUMER1_PKG,
-                    "android.os.lib.consumer1.UseSharedLibraryTest",
+                    "android.os.lib.consumer1.CookieTest",
                     "testAppCanSeeOnlyLibrariesItDependOn");
         } finally {
             getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
diff --git a/hostsidetests/security/Android.mk b/hostsidetests/security/Android.mk
index 2704850..ffb5adde 100644
--- a/hostsidetests/security/Android.mk
+++ b/hostsidetests/security/Android.mk
@@ -40,7 +40,7 @@
 
 selinux_plat_file_contexts := $(call intermediates-dir-for,ETC,plat_file_contexts)/plat_file_contexts
 
-selinux_general_property_contexts := $(call intermediates-dir-for,ETC,general_property_contexts)/general_property_contexts
+selinux_plat_property_contexts := $(call intermediates-dir-for,ETC,plat_property_contexts)/plat_property_contexts
 
 selinux_plat_service_contexts := $(call intermediates-dir-for,ETC,plat_service_contexts)/plat_service_contexts
 
@@ -49,8 +49,8 @@
     $(HOST_OUT_EXECUTABLES)/checkfc \
     $(selinux_plat_seapp_contexts) \
     $(selinux_plat_seapp_neverallows) \
-    $(selinux_general_file_contexts) \
-    $(selinux_general_property_contexts) \
+    $(selinux_plat_file_contexts) \
+    $(selinux_plat_property_contexts) \
     $(selinux_plat_service_contexts)
 
 selinux_general_policy := $(call intermediates-dir-for,ETC,general_sepolicy.conf)/general_sepolicy.conf
diff --git a/hostsidetests/security/src/android/security/cts/Poc16_10.java b/hostsidetests/security/src/android/security/cts/Poc16_10.java
index 94b442d..d04ebea 100644
--- a/hostsidetests/security/src/android/security/cts/Poc16_10.java
+++ b/hostsidetests/security/src/android/security/cts/Poc16_10.java
@@ -68,6 +68,7 @@
     /**
      *  b/30906694
      */
+    @SecurityTest
     public void testPocCVE_2016_6733() throws Exception {
         if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
             AdbUtils.runPoc("CVE-2016-6733", getDevice(), 60);
@@ -77,6 +78,7 @@
     /**
      *  b/30907120
      */
+    @SecurityTest
     public void testPocCVE_2016_6734() throws Exception {
         if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
             AdbUtils.runPoc("CVE-2016-6734", getDevice(), 60);
@@ -86,6 +88,7 @@
     /**
      *  b/30907701
      */
+    @SecurityTest
     public void testPocCVE_2016_6735() throws Exception {
         if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
             AdbUtils.runPoc("CVE-2016-6735", getDevice(), 60);
@@ -95,6 +98,7 @@
     /**
      *  b/30953284
      */
+    @SecurityTest
     public void testPocCVE_2016_6736() throws Exception {
         if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
             AdbUtils.runPoc("CVE-2016-6736", getDevice(), 60);
diff --git a/hostsidetests/security/src/android/security/cts/Poc16_12.java b/hostsidetests/security/src/android/security/cts/Poc16_12.java
index d0d25f0..a6160d5 100644
--- a/hostsidetests/security/src/android/security/cts/Poc16_12.java
+++ b/hostsidetests/security/src/android/security/cts/Poc16_12.java
@@ -59,6 +59,7 @@
     /**
      *  b/31799206
      */
+    @SecurityTest
     public void testPocCVE_2016_8426() throws Exception {
         if(containsDriver(getDevice(), "/dev/nvhost-gpu")) {
             AdbUtils.runPoc("CVE-2016-8426", getDevice(), 60);
@@ -68,6 +69,7 @@
     /**
      *  b/31799885
      */
+    @SecurityTest
     public void testPocCVE_2016_8427() throws Exception {
         if(containsDriver(getDevice(), "/dev/nvhost-gpu") ||
               containsDriver(getDevice(), "/dev/nvhost-dbg-gpu")) {
@@ -78,6 +80,7 @@
     /**
      *  b/31993456
      */
+    @SecurityTest
     public void testPocCVE_2016_8428() throws Exception {
         if(containsDriver(getDevice(), "/dev/nvmap")) {
             AdbUtils.runPoc("CVE-2016-8428", getDevice(), 60);
@@ -87,6 +90,7 @@
     /**
      *  b/32160775
      */
+    @SecurityTest
     public void testPocCVE_2016_8429() throws Exception {
         if(containsDriver(getDevice(), "/dev/nvmap")) {
             AdbUtils.runPoc("CVE-2016-8429", getDevice(), 60);
@@ -96,6 +100,7 @@
     /**
      *  b/32225180
      */
+    @SecurityTest
     public void testPocCVE_2016_8430() throws Exception {
         if(containsDriver(getDevice(), "/dev/nvhost-vic")) {
             AdbUtils.runPoc("CVE-2016-8430", getDevice(), 60);
@@ -105,6 +110,7 @@
    /**
      *  b/32402179
      */
+    @SecurityTest
     public void testPocCVE_2016_8431() throws Exception {
         if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
             AdbUtils.runPoc("CVE-2016-8431", getDevice(), 60);
@@ -114,6 +120,7 @@
     /**
      *  b/32447738
      */
+    @SecurityTest
     public void testPocCVE_2016_8432() throws Exception {
         if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
             AdbUtils.runPoc("CVE-2016-8432", getDevice(), 60);
@@ -123,6 +130,7 @@
     /**
      *  b/32125137
      */
+    @SecurityTest
     public void testPocCVE_2016_8434() throws Exception {
         if(containsDriver(getDevice(), "/dev/kgsl-3d0")) {
             AdbUtils.runPoc("CVE-2016-8434", getDevice(), 60);
@@ -132,6 +140,7 @@
     /**
      *  b/32700935
      */
+    @SecurityTest
     public void testPocCVE_2016_8435() throws Exception {
         enableAdbRoot(getDevice());
         if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
@@ -142,6 +151,7 @@
     /**
      *  b/31568617
      */
+    @SecurityTest
     public void testPocCVE_2016_9120() throws Exception {
         enableAdbRoot(getDevice());
         if(containsDriver(getDevice(), "/dev/ion")) {
@@ -153,6 +163,7 @@
     /**
      *  b/31225246
      */
+    @SecurityTest
     public void testPocCVE_2016_8412() throws Exception {
         enableAdbRoot(getDevice());
         if(containsDriver(getDevice(), "/dev/v4l-subdev7")) {
@@ -163,6 +174,7 @@
     /**
      *  b/31243641
      */
+    @SecurityTest
     public void testPocCVE_2016_8444() throws Exception {
         enableAdbRoot(getDevice());
         if(containsDriver(getDevice(), "/dev/v4l-subdev17")) {
@@ -173,6 +185,7 @@
     /**
      *  b/31791148
      */
+    @SecurityTest
     public void testPocCVE_2016_8448() throws Exception {
         enableAdbRoot(getDevice());
         if(containsDriver(getDevice(), "/dev/graphics/fb0")) {
@@ -183,6 +196,7 @@
     /**
      *  b/31798848
      */
+    @SecurityTest
     public void testPocCVE_2016_8449() throws Exception {
         enableAdbRoot(getDevice());
         if(containsDriver(getDevice(), "/dev/tegra_avpchannel")) {
@@ -193,6 +207,7 @@
     /**
      *  b/31668540
      */
+    @SecurityTest
     public void testPocCVE_2016_8460() throws Exception {
         if(containsDriver(getDevice(), "/dev/nvmap")) {
             String result = AdbUtils.runPoc("CVE-2016-8460", getDevice(), 60);
@@ -203,6 +218,7 @@
     /**
      *  b/32402548
      */
+    @SecurityTest
     public void testPocCVE_2017_0403() throws Exception {
         enableAdbRoot(getDevice());
         AdbUtils.runPoc("CVE-2017-0403", getDevice(), 60);
@@ -211,6 +227,7 @@
     /**
      *  b/32510733
      */
+    @SecurityTest
     public void testPocCVE_2017_0404() throws Exception {
         enableAdbRoot(getDevice());
         if(containsDriver(getDevice(), "/proc/asound/version")) {
@@ -221,6 +238,7 @@
     /**
      *  b/32178033
      */
+    @SecurityTest
     public void testPocCVE_2016_8451() throws Exception {
         enableAdbRoot(getDevice());
         String command =
@@ -231,6 +249,7 @@
     /**
      *  b/32659848
      */
+    @SecurityTest
     public void testPoc32659848() throws Exception {
         String command =
             "echo 18014398509481980 > /sys/kernel/debug/tracing/buffer_size_kb";
diff --git a/hostsidetests/security/src/android/security/cts/Poc17_01.java b/hostsidetests/security/src/android/security/cts/Poc17_01.java
index 88470b6..f8ed22a 100644
--- a/hostsidetests/security/src/android/security/cts/Poc17_01.java
+++ b/hostsidetests/security/src/android/security/cts/Poc17_01.java
@@ -34,6 +34,7 @@
    /**
      *  b/32636619
      */
+    @SecurityTest
     public void testPocCVE_2017_0429() throws Exception {
         if(containsDriver(getDevice(), "/dev/nvhost-as-gpu")) {
             enableAdbRoot(getDevice());
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index b3f4ad7..1799fb8 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -347,12 +347,12 @@
         /* obtain property_contexts file from running device */
         devicePcFile = File.createTempFile("property_contexts", ".tmp");
         devicePcFile.deleteOnExit();
-        mDevice.pullFile("/property_contexts", devicePcFile);
+        mDevice.pullFile("/plat_property_contexts", devicePcFile);
 
         /* retrieve the AOSP property_contexts file from jar */
-        aospPcFile = copyResourceToTempFile("/general_property_contexts");
+        aospPcFile = copyResourceToTempFile("/plat_property_contexts");
 
-        assertFileStartsWith(aospPcFile, devicePcFile);
+        assertFileEquals(aospPcFile, devicePcFile);
     }
 
     /**
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
index 1c4050c..b091bae 100755
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
@@ -29,6 +29,12 @@
                 android:resizeableActivity="true"
                 android:exported="true"
         />
+        <activity android:name=".ResumeWhilePausingActivity"
+                android:allowEmbedded="true"
+                android:resumeWhilePausing="true"
+                android:taskAffinity=""
+                android:exported="true"
+        />
         <activity android:name=".ResizeableActivity"
                 android:resizeableActivity="true"
                 android:exported="true"
@@ -99,7 +105,7 @@
                   android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
                   android:exported="true"
         />
-        <activity android:name=".LaunchTapToFinishPipActivity"
+        <activity android:name=".LaunchEnterPipActivity"
                   android:resizeableActivity="false"
                   android:supportsPictureInPicture="true"
                   androidprv:alwaysFocusable="true"
@@ -258,6 +264,10 @@
                   android:screenOrientation="landscape"
                   android:documentLaunchMode="always"
         />
+        <activity android:name=".MoveTaskToBackActivity"
+                  android:exported="true"
+                  android:launchMode="singleInstance"
+        />
         <receiver
             android:name=".LaunchBroadcastReceiver"
             android:enabled="true"
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
index 6dab9dd..5eb6514 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.WindowManager;
 
 /**
@@ -33,6 +34,7 @@
 public class BroadcastReceiverActivity extends Activity {
 
     public static final String ACTION_TRIGGER_BROADCAST = "trigger_broadcast";
+    private static final String TAG = BroadcastReceiverActivity.class.getSimpleName();
 
     private TestBroadcastReceiver mBroadcastReceiver = new TestBroadcastReceiver();
 
@@ -56,6 +58,8 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final Bundle extras = intent.getExtras();
+            Log.i(TAG, "onReceive: extras=" + extras);
+
             if (extras == null) {
                 return;
             }
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
similarity index 77%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
rename to hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
index 4e3fc89..322927e 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -19,10 +19,10 @@
 import android.app.Activity;
 import android.graphics.Rect;
 
-public class LaunchTapToFinishPipActivity extends Activity {
+public class LaunchEnterPipActivity extends Activity {
     @Override
     protected void onResume() {
         super.onResume();
-        PipActivity.launchActivity(this, new Rect(0, 0, 500, 500), true /* tapToLaunch */);
+        PipActivity.launchEnterPipActivity(this);
     }
 }
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchImeWithPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchImeWithPipActivity.java
index 67bbb63..52e0da8 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchImeWithPipActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchImeWithPipActivity.java
@@ -32,7 +32,7 @@
         final Display display = wm.getDefaultDisplay();
         final Point displaySize = new Point();
         display.getRealSize(displaySize);
-        PipActivity.launchActivity(this, new Rect(0, displaySize.y - 150, 150, displaySize.y),
-                false /* tapToLaunch */);
+        PipActivity.launchActivityIntoPinnedStack(this,
+                new Rect(0, displaySize.y - 150, 150, displaySize.y));
     }
 }
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java
new file mode 100644
index 0000000..6c47fb7
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java
@@ -0,0 +1,62 @@
+/*
+ * 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.server.cts;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Activity that finishes itself using "moveTaskToBack".
+ */
+public class MoveTaskToBackActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = MoveTaskToBackActivity.class.getSimpleName();
+
+    private String mFinishPoint;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        final Intent intent = getIntent();
+        mFinishPoint = intent.getExtras().getString("finish_point");
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        if (mFinishPoint.equals("on_pause")) {
+            moveTaskToBack(true /* nonRoot */);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        if (mFinishPoint.equals("on_stop")) {
+            moveTaskToBack(true /* nonRoot */);
+        }
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
index 8a49ed6..4d2f24f 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
@@ -30,6 +30,8 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
 import android.view.WindowManager;
 
 import java.util.Collections;
@@ -57,6 +59,13 @@
     private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
     // Shows this activity over the keyguard
     private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
+    // Adds an assertion that we do not ever get onStop() before we enter picture in picture
+    private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
+    // The amount to delay to artificially introduce in onPause() (before EXTRA_ENTER_PIP_ON_PAUSE
+    // is processed)
+    private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
+
+    private boolean mEnteredPictureInPicture;
 
     private Handler mHandler = new Handler();
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -137,6 +146,11 @@
         super.onPause();
         unregisterReceiver(mReceiver);
 
+        // Pause if requested
+        if (getIntent().hasExtra(EXTRA_ON_PAUSE_DELAY)) {
+            SystemClock.sleep(Long.valueOf(getIntent().getStringExtra(EXTRA_ON_PAUSE_DELAY)));
+        }
+
         // Enter PIP on move to background
         if (getIntent().hasExtra(EXTRA_ENTER_PIP_ON_PAUSE)) {
             enterPictureInPictureMode();
@@ -144,9 +158,24 @@
     }
 
     @Override
+    protected void onStop() {
+        super.onStop();
+        if (getIntent().hasExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP) && !mEnteredPictureInPicture) {
+            Log.w("PipActivity", "Unexpected onStop() called before entering picture-in-picture");
+            finish();
+        }
+    }
+
+    @Override
     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
         super.onPictureInPictureModeChanged(isInPictureInPictureMode);
 
+        // Mark that we've entered picture-in-picture so that we can stop checking for
+        // EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP
+        if (isInPictureInPictureMode) {
+            mEnteredPictureInPicture = true;
+        }
+
         if (!isInPictureInPictureMode && getIntent().hasExtra(EXTRA_REENTER_PIP_ON_EXIT)) {
             // This call to re-enter PIP can happen too quickly (host side tests can have difficulty
             // checking that the stacks ever changed). Therefor, we need to delay here slightly to
@@ -158,16 +187,27 @@
     }
 
     /**
-     * Launches a new instance of the PipActivity.
+     * Launches a new instance of the PipActivity directly into the pinned stack.
      */
-    static void launchActivity(Activity caller, Rect bounds, boolean tapToFinish) {
+    static void launchActivityIntoPinnedStack(Activity caller, Rect bounds) {
         final Intent intent = new Intent(caller, PipActivity.class);
         intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(EXTRA_TAP_TO_FINISH, tapToFinish);
+        intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
 
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchBounds(bounds);
         options.setLaunchStackId(4 /* ActivityManager.StackId.PINNED_STACK_ID */);
         caller.startActivity(intent, options.toBundle());
     }
+
+    /**
+     * Launches a new instance of the PipActivity that will automatically enter PiP.
+     */
+    static void launchEnterPipActivity(Activity caller) {
+        final Intent intent = new Intent(caller, PipActivity.class);
+        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRA_ENTER_PIP, "true");
+        intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+        caller.startActivity(intent);
+    }
 }
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
similarity index 63%
copy from hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
copy to hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
index 4e3fc89..578d7e5 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,18 +11,13 @@
  * 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
+ * limitations under the License.
  */
 
 package android.server.cts;
 
 import android.app.Activity;
-import android.graphics.Rect;
 
-public class LaunchTapToFinishPipActivity extends Activity {
-    @Override
-    protected void onResume() {
-        super.onResume();
-        PipActivity.launchActivity(this, new Rect(0, 0, 500, 500), true /* tapToLaunch */);
-    }
+public class ResumeWhilePausingActivity extends Activity {
+    // Empty
 }
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
index e84d5d4..68c34bf 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
@@ -27,13 +27,17 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.server.cts.TestActivity;
+import android.util.Log;
 
 /** Utility class which contains common code for launching activities. */
 public class ActivityLauncher {
+    private static final String TAG = ActivityLauncher.class.getSimpleName();
+
     public static void launchActivityFromExtras(final Context context, Bundle extras) {
         if (extras == null) {
             extras = new Bundle();
         }
+        Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
 
         final Intent newIntent = new Intent();
         final String targetActivity = extras.getString("target_activity");
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
index 0d5f61a..a7d4042 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
@@ -28,7 +28,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityAndWindowManagerOverrideConfigTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityAndWindowManagerOverrideConfigTests
  */
 public class ActivityAndWindowManagerOverrideConfigTests extends ActivityManagerTestBase {
     private static final String TEST_ACTIVITY_NAME = "LogConfigurationActivity";
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
index 6023039..c766caa 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
@@ -16,12 +16,14 @@
 
 package android.server.cts;
 
+import static android.server.cts.ActivityManagerState.STATE_RESUMED;
+
 import java.lang.Exception;
 import java.lang.String;
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerActivityVisibilityTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerActivityVisibilityTests
  */
 public class ActivityManagerActivityVisibilityTests extends ActivityManagerTestBase {
     private static final String TRANSLUCENT_ACTIVITY = "AlwaysFocusablePipActivity";
@@ -31,6 +33,7 @@
     private static final String TRANSLUCENT_ACTIVITY_NAME = "TranslucentActivity";
     private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
     private static final String TURN_SCREEN_ON_ACTIVITY_NAME = "TurnScreenOnActivity";
+    private static final String MOVE_TASK_TO_BACK_ACTIVITY_NAME = "MoveTaskToBackActivity";
 
     public void testVisibleBehindHomeActivity() throws Exception {
         launchActivity(VISIBLE_BEHIND_ACTIVITY);
@@ -161,9 +164,41 @@
         mAmWmState.computeState(mDevice, new String[] { TEST_ACTIVITY_NAME });
         mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
         // Finish activity in non-focused (docked) stack.
-        executeShellCommand("am broadcast -a trigger_broadcast --ez finish true");
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
         mAmWmState.computeState(mDevice, new String[] { LAUNCHING_ACTIVITY });
         mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
         mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, false);
     }
+
+    public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
+        performFinishActivityWithMoveTaskToBack("on_pause");
+    }
+
+    public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
+        performFinishActivityWithMoveTaskToBack("on_stop");
+    }
+
+    private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
+        // Make sure home activity is visible.
+        launchHomeActivity();
+        mAmWmState.assertHomeActivityVisible(true /* visible */);
+
+        // Launch an activity that calls "moveTaskToBack" to finish itself.
+        launchActivity(MOVE_TASK_TO_BACK_ACTIVITY_NAME, "finish_point", finishPoint);
+        mAmWmState.waitForValidState(mDevice, MOVE_TASK_TO_BACK_ACTIVITY_NAME);
+        mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, true);
+
+        // Launch a different activity on top.
+        launchActivity(BROADCAST_RECEIVER_ACTIVITY);
+        mAmWmState.waitForActivityState(mDevice, BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
+        mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, false);
+        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
+
+        // Finish the top-most activity.
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+        // Home must be visible.
+        mAmWmState.waitForHomeActivityVisible(mDevice);
+        mAmWmState.assertHomeActivityVisible(true /* visible */);
+    }
 }
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
index 0f77868..b31fb16 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
@@ -23,7 +23,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerAmStartOptionsTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAmStartOptionsTests
  */
 public class ActivityManagerAmStartOptionsTests extends ActivityManagerTestBase {
 
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
index 8636415..925994a 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
@@ -21,7 +21,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerAppConfigurationTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAppConfigurationTests
  */
 public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
     private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
index 45630e4..ef296ad 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
@@ -18,7 +18,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerConfigChangeTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerConfigChangeTests
  */
 public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
 
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
index 09b4edf..09bbb02 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
@@ -32,7 +32,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerDisplayTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDisplayTests
  */
 public class ActivityManagerDisplayTests extends ActivityManagerTestBase {
     private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity processes";
@@ -314,7 +314,7 @@
         sleepDevice();
 
         // Finish activity on secondary display.
-        executeShellCommand("am broadcast -a trigger_broadcast --ez finish true");
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
 
         // Unlock and check if the focus is switched back to primary display.
         wakeUpAndUnlockDevice();
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
index 74741d9..ec59164 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
@@ -20,7 +20,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerDockedStackTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDockedStackTests
  */
 public class ActivityManagerDockedStackTests extends ActivityManagerTestBase {
 
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
index b7dff24..a3d58f2 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
@@ -21,7 +21,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerFreeformStackTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerFreeformStackTests
  */
 public class ActivityManagerFreeformStackTests extends ActivityManagerTestBase {
 
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
index a4dfcee..b7df926 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
@@ -34,7 +34,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerManifestLayoutTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerManifestLayoutTests
  */
 public class ActivityManagerManifestLayoutTests extends ActivityManagerTestBase {
 
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
index e6be28c..e2c8534 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
@@ -31,26 +31,33 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerPinnedStackTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests
  */
 public class ActivityManagerPinnedStackTests extends ActivityManagerTestBase {
     private static final String TEST_ACTIVITY = "TestActivity";
     private static final String NON_RESIZEABLE_ACTIVITY = "NonResizeableActivity";
+    private static final String RESUME_WHILE_PAUSING_ACTIVITY = "ResumeWhilePausingActivity";
     private static final String PIP_ACTIVITY = "PipActivity";
     private static final String ALWAYS_FOCUSABLE_PIP_ACTIVITY = "AlwaysFocusablePipActivity";
     private static final String LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY =
             "LaunchIntoPinnedStackPipActivity";
-    private static final String LAUNCH_TAP_TO_FINISH_PIP_ACTIVITY = "LaunchTapToFinishPipActivity";
     private static final String LAUNCH_IME_WITH_PIP_ACTIVITY = "LaunchImeWithPipActivity";
+    private static final String LAUNCHER_ENTER_PIP_ACTIVITY = "LaunchEnterPipActivity";
     private static final String PIP_ON_STOP_ACTIVITY = "PipOnStopActivity";
 
     private static final String EXTRA_ENTER_PIP = "enter_pip";
     private static final String EXTRA_ENTER_PIP_ASPECT_RATIO = "enter_pip_aspect_ratio";
     private static final String EXTRA_SET_ASPECT_RATIO = "set_aspect_ratio";
     private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
+    private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
     private static final String EXTRA_START_ACTIVITY = "start_activity";
     private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
     private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
+    private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
+    private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
+
+    private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
+            "android.server.cts.PipActivity.enter_pip";
 
     private static final int APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
     private static final int APP_OPS_MODE_ALLOWED = 0;
@@ -87,8 +94,12 @@
     }
 
     public void testNonTappablePipActivity() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch the tap-to-finish activity at a specific place
-        launchActivity(LAUNCH_TAP_TO_FINISH_PIP_ACTIVITY);
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_TAP_TO_FINISH, "true");
         mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
         assertPinnedStackExists();
 
@@ -101,6 +112,8 @@
     }
 
     public void testPinnedStackDefaultBounds() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch a PIP activity
         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
 
@@ -123,6 +136,8 @@
     }
 
     public void testPinnedStackMovementBounds() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch a PIP activity
         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
 
@@ -145,10 +160,14 @@
     }
 
     public void testPinnedStackOutOfBoundsInsetsNonNegative() throws Exception {
+        if (!supportsPip()) return;
+
         final WindowManagerState wmState = mAmWmState.getWmState();
 
         // Launch an activity into the pinned stack
-        launchActivity(LAUNCH_TAP_TO_FINISH_PIP_ACTIVITY);
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_TAP_TO_FINISH, "true");
         mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
 
         // Get the display dimensions
@@ -169,8 +188,12 @@
     }
 
     public void testPinnedStackInBoundsAfterRotation() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch an activity into the pinned stack
-        launchActivity(LAUNCH_TAP_TO_FINISH_PIP_ACTIVITY);
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_TAP_TO_FINISH, "true");
         mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
 
         // Ensure that the PIP stack is fully visible in each orientation
@@ -186,6 +209,8 @@
     }
 
     public void testPinnedStackOffsetForIME() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch an activity which shows an IME
         launchActivity(LAUNCH_IME_WITH_PIP_ACTIVITY);
         mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
@@ -202,6 +227,8 @@
     }
 
     public void testEnterPipAspectRatio() throws Exception {
+        if (!supportsPip()) return;
+
         launchActivity(PIP_ACTIVITY,
                 EXTRA_ENTER_PIP, "true",
                 EXTRA_ENTER_PIP_ASPECT_RATIO, Float.toString(VALID_ASPECT_RATIO));
@@ -215,6 +242,8 @@
     }
 
     public void testResizePipAspectRatio() throws Exception {
+        if (!supportsPip()) return;
+
         launchActivity(PIP_ACTIVITY,
                 EXTRA_ENTER_PIP, "true",
                 EXTRA_SET_ASPECT_RATIO, Float.toString(VALID_ASPECT_RATIO));
@@ -234,6 +263,8 @@
     }
 
     public void testEnterPipExtremeAspectRatios() throws Exception {
+        if (!supportsPip()) return;
+
         // Assert that we could not create a pinned stack with an extreme aspect ratio
         launchActivity(PIP_ACTIVITY,
                 EXTRA_ENTER_PIP, "true",
@@ -242,6 +273,8 @@
     }
 
     public void testSetPipExtremeAspectRatios() throws Exception {
+        if (!supportsPip()) return;
+
         // Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
         // fails (the aspect ratio remains the same)
         launchActivity(PIP_ACTIVITY,
@@ -256,6 +289,8 @@
     }
 
     public void testDisallowPipLaunchFromStoppedActivity() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch the bottom pip activity
         launchActivity(PIP_ON_STOP_ACTIVITY);
         mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
@@ -268,6 +303,8 @@
     }
 
     public void testAutoEnterPictureInPicture() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch a test activity so that we're not over home
         launchActivity(TEST_ACTIVITY);
 
@@ -281,6 +318,8 @@
     }
 
     public void testAutoEnterPictureInPictureLaunchActivity() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch a test activity so that we're not over home
         launchActivity(TEST_ACTIVITY);
 
@@ -300,6 +339,8 @@
     }
 
     public void testAutoEnterPictureInPictureFinish() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch a test activity so that we're not over home
         launchActivity(TEST_ACTIVITY);
 
@@ -313,6 +354,8 @@
     }
 
     public void testAutoEnterPictureInPictureAspectRatio() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch the PIP activity on pause, and set the aspect ratio
         launchActivity(PIP_ACTIVITY,
                 EXTRA_ENTER_PIP_ON_PAUSE, "true",
@@ -336,6 +379,8 @@
     }
 
     public void testAutoEnterPictureInPictureOverPip() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch another PIP activity
         launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
         mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
@@ -357,6 +402,8 @@
     }
 
     public void testPipUnPipOverHome() throws Exception {
+        if (!supportsPip()) return;
+
         // Go home
         launchHomeActivity();
         // Launch an auto pip activity
@@ -365,11 +412,8 @@
                 EXTRA_REENTER_PIP_ON_EXIT, "true");
         assertPinnedStackExists();
 
-        // Tap the screen at a known location in the pinned stack bounds to trigger the activity
-        // to exit and re-enter pip
-        // TODO: add channel for expanding the PIP, but for now, just force-tap twice
-        tapToFinishPip();
-        tapToFinishPip();
+        // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
+        launchActivity(PIP_ACTIVITY);
         mAmWmState.waitForWithAmState(mDevice, (amState) -> {
             return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == FULLSCREEN_WORKSPACE_STACK_ID;
         }, "Waiting for PIP to exit to fullscreen");
@@ -380,6 +424,8 @@
     }
 
     public void testPipUnPipOverApp() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch a test activity so that we're not over home
         launchActivity(TEST_ACTIVITY);
 
@@ -389,11 +435,8 @@
                 EXTRA_REENTER_PIP_ON_EXIT, "true");
         assertPinnedStackExists();
 
-        // Tap the screen at a known location in the pinned stack bounds to trigger the activity
-        // to exit and re-enter pip
-        // TODO: add channel for expanding the PIP, but for now, just force-tap twice
-        tapToFinishPip();
-        tapToFinishPip();
+        // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
+        launchActivity(PIP_ACTIVITY);
         mAmWmState.waitForWithAmState(mDevice, (amState) -> {
             return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == FULLSCREEN_WORKSPACE_STACK_ID;
         }, "Waiting for PIP to exit to fullscreen");
@@ -405,6 +448,8 @@
     }
 
     public void testRemovePipWithNoFullscreenStack() throws Exception {
+        if (!supportsPip()) return;
+
         // Start with a clean slate, remove all the stacks but home
         removeStacks(ALL_STACK_IDS_BUT_HOME);
 
@@ -425,6 +470,8 @@
     }
 
     public void testRemovePipWithVisibleFullscreenStack() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch a fullscreen activity, and a pip activity over that
         launchActivity(TEST_ACTIVITY);
         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
@@ -447,6 +494,8 @@
     }
 
     public void testRemovePipWithHiddenFullscreenStack() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
         // launch a pip activity over home
         launchActivity(TEST_ACTIVITY);
@@ -470,6 +519,8 @@
     }
 
     public void testPinnedStackAlwaysOnTop() throws Exception {
+        if (!supportsPip()) return;
+
         // Launch activity into pinned stack and assert it's on top.
         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
         assertPinnedStackExists();
@@ -487,6 +538,8 @@
     }
 
     public void testAppOpsDenyPipOnPause() throws Exception {
+        if (!supportsPip()) return;
+
         // Disable enter-pip-on-hide and try to enter pip
         setAppOpsOpToMode(ActivityManagerTestBase.componentName,
                 APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE, APP_OPS_MODE_IGNORED);
@@ -504,6 +557,64 @@
                 APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE, APP_OPS_MODE_ALLOWED);
     }
 
+    public void testEnterPipFromTaskWithMultipleActivities() throws Exception {
+        if (!supportsPip()) return;
+
+        // Try to enter picture-in-picture from an activity that has more than one activity in the
+        // task and ensure that it works
+        launchActivity(LAUNCHER_ENTER_PIP_ACTIVITY);
+        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
+        assertPinnedStackExists();
+    }
+
+    public void testEnterPipWithResumeWhilePausingActivityNoStop() throws Exception {
+        if (!supportsPip()) return;
+
+        /*
+         * Launch the resumeWhilePausing activity and ensure that the PiP activity did not get
+         * stopped and actually went into the pinned stack.
+         *
+         * Note that this is a workaround because to trigger the path that we want to happen in
+         * activity manager, we need to add the leaving activity to the stopping state, which only
+         * happens when a hidden stack is brought forward. Normally, this happens when you go home,
+         * but since we can't launch into the home stack directly, we have a workaround.
+         *
+         * 1) Launch an activity in a new dynamic stack
+         * 2) Resize the dynamic stack to non-fullscreen bounds
+         * 3) Start the PiP activity that will enter picture-in-picture when paused in the
+         *    fullscreen stack
+         * 4) Bring the activity in the dynamic stack forward to trigger PiP
+         */
+        int stackId = launchActivityInNewDynamicStack(RESUME_WHILE_PAUSING_ACTIVITY);
+        resizeStack(stackId, 0, 0, 500, 500);
+        // Launch an activity that will enter PiP when it is paused with a delay that is long enough
+        // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
+        // trigger the current system pause timeout (currently 500ms)
+        launchActivityInStack(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
+                EXTRA_ENTER_PIP_ON_PAUSE, "true",
+                EXTRA_ON_PAUSE_DELAY, "350",
+                EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+        launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
+        assertPinnedStackExists();
+    }
+
+    public void testDisallowEnterPipActivityLocked() throws Exception {
+        if (!supportsPip()) return;
+
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+        ActivityTask task =
+                mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID).getTopTask();
+
+        // Lock the task and ensure that we can't enter picture-in-picture both explicitly and
+        // when paused
+        executeShellCommand("am task lock " + task.mTaskId);
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+        assertPinnedStackDoesNotExist();
+        launchHomeActivity();
+        assertPinnedStackDoesNotExist();
+        executeShellCommand("am task lock stop");
+    }
+
     /**
      * Asserts that the pinned stack bounds does not intersect with the IME bounds.
      */
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
index 3c1ba76..59d632a 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
@@ -31,7 +31,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerReplaceWindowTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerReplaceWindowTests
  */
 public class ActivityManagerReplaceWindowTests extends ActivityManagerTestBase {
 
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
index 7e79a85..7b366d4 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
@@ -36,7 +36,7 @@
  * The exact animation is unspecified and can be overridden.
  *
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerTransitionSelectionTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerTransitionSelectionTests
  */
 public class ActivityManagerTransitionSelectionTests extends ActivityManagerTestBase {
 
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
index 1179b12..456225a 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
@@ -18,7 +18,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.AnimationBackgroundTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.AnimationBackgroundTests
  */
 public class AnimationBackgroundTests extends ActivityManagerTestBase {
 
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
index 771eab2..0ebbfe2 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
@@ -25,7 +25,7 @@
  * an unsupported smallest width.
  *
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.DisplaySizeTest
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.DisplaySizeTest
  */
 public class DisplaySizeTest extends DeviceTestCase {
     private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
index c30990d..a445b54 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
@@ -18,7 +18,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.KeyguardLockedTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardLockedTests
  */
 public class KeyguardLockedTests extends KeyguardTestBase {
 
@@ -123,7 +123,7 @@
     }
 
     public void testEnterPipOverKeyguard() throws Exception {
-        if (!isHandheld()) {
+        if (!isHandheld() || !supportsPip()) {
             return;
         }
 
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
index 093639a..0128e2c 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
@@ -20,7 +20,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.KeyguardTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardTests
  */
 public class KeyguardTests extends KeyguardTestBase {
 
@@ -256,7 +256,7 @@
         launchActivity("KeyguardLockActivity");
         mAmWmState.computeState(mDevice, new String[] { "KeyguardLockActivity" });
         mAmWmState.assertVisibility("KeyguardLockActivity", true);
-        executeShellCommand("am broadcast -a trigger_broadcast --ez finish true");
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
         mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
         assertShowingAndNotOccluded();
     }
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
index 4aa663a..e67cdd9 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
@@ -24,7 +24,7 @@
 
 /**
  * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.KeyguardTransitionTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardTransitionTests
  */
 public class KeyguardTransitionTests extends ActivityManagerTestBase {
 
diff --git a/hostsidetests/services/activityandwindowmanager/util/run-test b/hostsidetests/services/activityandwindowmanager/util/run-test
index 131fbc5..46cdc44 100755
--- a/hostsidetests/services/activityandwindowmanager/util/run-test
+++ b/hostsidetests/services/activityandwindowmanager/util/run-test
@@ -2,11 +2,11 @@
 
 # Helper script for running CTS tests with all the right params.
 # Example usage:
-#  run-test <package_name>  // To run all the tests in a package.
-#  run-test <package_name>.<class_name>  // To run all the tests in a class.
-#  run-test <package_name>.<class_name>#<method_name>  // To run a specific test in a class.
+#  run-test <module> <package_name>  // To run all the tests in a package.
+#  run-test <module> <package_name>.<class_name>  // To run all the tests in a class.
+#  run-test <module> <package_name>.<class_name>#<method_name>  // To run a specific test in a class.
 
 echo " "
-echo "Running test...$1"
+echo "Running module $1 test...$2"
 echo " "
-cts-tradefed run commandAndExit cts-dev --module CtsServicesHostTestCases --test $1 --disable-reboot --skip-device-info --skip-all-system-status-check --skip-preconditions
+cts-tradefed run commandAndExit cts-dev --module $1 --test $2 --disable-reboot --skip-device-info --skip-all-system-status-check --skip-preconditions
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
index 89d554f..a1d7553 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
@@ -27,18 +27,24 @@
 import java.lang.Integer;
 import java.lang.String;
 import java.util.HashSet;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import static android.server.cts.StateLogger.log;
 import static android.server.cts.StateLogger.logE;
 
+import android.server.cts.ActivityManagerState.ActivityStack;
+
 public abstract class ActivityManagerTestBase extends DeviceTestCase {
     private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
     private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
 
     // Constants copied from ActivityManager.StackId. If they are changed there, these must be
     // updated.
+    /** Invalid stack ID. */
+    public static final int INVALID_STACK_ID = -1;
+
     /** First static stack ID. */
     public static final int FIRST_STATIC_STACK_ID = 0;
 
@@ -87,7 +93,12 @@
     static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
     static final String BROADCAST_RECEIVER_ACTIVITY = "BroadcastReceiverActivity";
 
+    /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
+    static final String FINISH_ACTIVITY_BROADCAST
+            = "am broadcast -a trigger_broadcast --ez finish true";
+
     private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
+    private static final String AM_RESIZE_STACK = "am stack resize ";
 
     static final String AM_MOVE_TASK = "am stack move-task ";
 
@@ -253,6 +264,37 @@
         mAmWmState.waitForValidState(mDevice, targetActivityName);
     }
 
+    /**
+     * Starts an activity in a new stack.
+     * @return the stack id of the newly created stack.
+     */
+    protected int launchActivityInNewDynamicStack(final String activityName) throws Exception {
+        HashSet<Integer> stackIds = getStackIds();
+        executeShellCommand("am stack start " + ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID
+                + " " + getActivityComponentName(activityName));
+        HashSet<Integer> newStackIds = getStackIds();
+        newStackIds.removeAll(stackIds);
+        if (newStackIds.isEmpty()) {
+            return INVALID_STACK_ID;
+        } else {
+            assertTrue(newStackIds.size() == 1);
+            return newStackIds.iterator().next();
+        }
+    }
+
+    /**
+     * Returns the set of stack ids.
+     */
+    private HashSet<Integer> getStackIds() throws Exception {
+        mAmWmState.computeState(mDevice, null);
+        final List<ActivityStack> stacks = mAmWmState.getAmState().getStacks();
+        final HashSet<Integer> stackIds = new HashSet<>();
+        for (ActivityStack s : stacks) {
+            stackIds.add(s.mStackId);
+        }
+        return stackIds;
+    }
+
     protected void launchHomeActivity()
             throws Exception {
         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
@@ -308,8 +350,9 @@
         mAmWmState.waitForValidState(mDevice, targetActivityName);
     }
 
-    protected void launchActivityInStack(String activityName, int stackId) throws Exception {
-        executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId);
+    protected void launchActivityInStack(String activityName, int stackId,
+            final String... keyValuePairs) throws Exception {
+        executeShellCommand(getAmStartCmd(activityName, keyValuePairs) + " --stack " + stackId);
 
         mAmWmState.waitForValidState(mDevice, activityName, stackId);
     }
@@ -358,6 +401,12 @@
                 + " 0 0 " + taskWidth + " " + taskHeight);
     }
 
+    protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
+            int stackHeight) throws DeviceNotAvailableException {
+        executeShellCommand(AM_RESIZE_STACK + String.format("%d %d %d %d %d", stackId, stackLeft,
+                stackTop, stackWidth, stackHeight));
+    }
+
     protected void pressHomeButton() throws DeviceNotAvailableException {
         executeShellCommand(INPUT_KEYEVENT_HOME);
     }
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
index 63d0503..878f081 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
@@ -27,6 +27,7 @@
 import java.awt.*;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -341,8 +342,7 @@
         }
     }
 
-    public void getMatchingVisibleWindowState(final String windowName,
-            List<WindowState> windowList) {
+    void getMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
         windowList.clear();
         for (WindowState ws : mWindowStates) {
             if (ws.isShown() && windowName.equals(ws.getName())) {
@@ -351,6 +351,40 @@
         }
     }
 
+    WindowState getWindowByPackageName(String packageName, int windowType) {
+        for (WindowState ws : mWindowStates) {
+            final String name = ws.getName();
+            if (name == null || !name.contains(packageName)) {
+                continue;
+            }
+            if (windowType != ws.getType()) {
+                continue;
+            }
+            return ws;
+        }
+
+        return null;
+    }
+
+    void getWindowsByPackageName(String packageName, List<Integer> restrictToTypeList,
+            List<WindowState> outWindowList) {
+        outWindowList.clear();
+        for (WindowState ws : mWindowStates) {
+            final String name = ws.getName();
+            if (name == null || !name.contains(packageName)) {
+                continue;
+            }
+            if (restrictToTypeList != null && !restrictToTypeList.contains(ws.getType())) {
+                continue;
+            }
+            outWindowList.add(ws);
+        }
+    }
+
+    void sortWindowsByLayer(List<WindowState> windows) {
+        windows.sort(Comparator.comparingInt(WindowState::getLayer));
+    }
+
     WindowState getWindowStateForAppToken(String appToken) {
         for (WindowState ws : mWindowStates) {
             if (ws.getToken().equals(appToken)) {
@@ -409,10 +443,13 @@
         return false;
     }
 
+    /** Check if at least one window which matches provided window name is visible. */
     boolean isWindowVisible(String windowName) {
         for (WindowState window : mWindowStates) {
             if (window.getName().equals(windowName)) {
-                return window.isShown();
+                if (window.isShown()) {
+                    return true;
+                }
             }
         }
         return false;
@@ -1043,7 +1080,7 @@
         @Override
         public String toString() {
             return "WindowState: {" + mAppToken + " " + mName
-                    + getWindowTypeSuffix(mWindowType) + "}"
+                    + getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
                     + " cf=" + mContainingFrame + " pf=" + mParentFrame;
         }
     }
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
index 67c4879..f1d8397 100644
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
@@ -13,13 +13,15 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Config for CTS drag and drop host test cases">
+<configuration description="Config for CTS window manager host test cases">
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsDragAndDropSourceApp.apk" />
         <option name="test-file-name" value="CtsDragAndDropTargetApp.apk" />
         <option name="test-file-name" value="CtsDragAndDropTargetAppSdk23.apk" />
         <option name="test-file-name" value="CtsDeviceWindowFramesTestApp.apk" />
+        <option name="test-file-name" value="CtsDeviceAlertWindowTestApp.apk" />
+        <option name="test-file-name" value="CtsDeviceAlertWindowTestAppSdk25.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsWindowManagerHostTestCases.jar" />
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk
new file mode 100644
index 0000000..a0f71dd
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-java-files-under, ../alertwindowappsdk25/src) \
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsDeviceAlertWindowTestApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml
new file mode 100755
index 0000000..5534e7e
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          package="android.server.alertwindowapp">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application>
+        <activity android:name=".AlertWindowTestActivity"
+                android:windowSoftInputMode="stateAlwaysVisible"
+                android:exported="true"
+        />
+    </application>
+</manifest>
+
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java
new file mode 100644
index 0000000..db007e2
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.alertwindowapp;
+
+import android.os.Bundle;
+import android.server.alertwindowappsdk25.AlertWindowTestBaseActivity;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+public class AlertWindowTestActivity extends AlertWindowTestBaseActivity {
+    private static final int[] ALERT_WINDOW_TYPES = {
+            TYPE_PHONE,
+            TYPE_PRIORITY_PHONE,
+            TYPE_SYSTEM_ALERT,
+            TYPE_SYSTEM_ERROR,
+            TYPE_SYSTEM_OVERLAY,
+            TYPE_APPLICATION_OVERLAY
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        createAllAlertWindows(getPackageName());
+    }
+
+    @Override
+    protected int[] getAlertWindowTypes() {
+        return ALERT_WINDOW_TYPES;
+    }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk
new file mode 100644
index 0000000..940f7a6
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk
@@ -0,0 +1,32 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+
+LOCAL_SDK_VERSION := 25
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsDeviceAlertWindowTestAppSdk25
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml
new file mode 100755
index 0000000..7b08dae
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.server.alertwindowappsdk25">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application>
+        <activity android:name=".AlertWindowTestActivitySdk25"
+                android:windowSoftInputMode="stateAlwaysVisible"
+                android:exported="true"
+        />
+    </application>
+</manifest>
+
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
new file mode 100644
index 0000000..046879f
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
@@ -0,0 +1,46 @@
+/*
+ * 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.server.alertwindowappsdk25;
+
+import android.os.Bundle;
+
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+public class AlertWindowTestActivitySdk25 extends AlertWindowTestBaseActivity {
+    private static final int[] ALERT_WINDOW_TYPES = {
+            TYPE_PHONE,
+            TYPE_PRIORITY_PHONE,
+            TYPE_SYSTEM_ALERT,
+            TYPE_SYSTEM_ERROR,
+            TYPE_SYSTEM_OVERLAY
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        createAllAlertWindows(getPackageName());
+    }
+
+    @Override
+    protected int[] getAlertWindowTypes() {
+        return ALERT_WINDOW_TYPES;
+    }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java
new file mode 100644
index 0000000..2d0dad0
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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.server.alertwindowappsdk25;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+
+public abstract class AlertWindowTestBaseActivity extends Activity {
+
+    protected void createAllAlertWindows(String windowName) {
+        final int[] alertWindowTypes = getAlertWindowTypes();
+        for (int type : alertWindowTypes) {
+            try {
+                createAlertWindow(type, windowName);
+            } catch (Exception e) {
+                Log.e("AlertWindowTestBaseActivity", "Can't create type=" + type, e);
+            }
+        }
+    }
+
+    protected void createAlertWindow(int type) {
+        createAlertWindow(type, getPackageName());
+    }
+
+    protected void createAlertWindow(int type, String windowName) {
+        if (!isSystemAlertWindowType(type)) {
+            throw new IllegalArgumentException("Well...you are not an alert window type=" + type);
+        }
+
+        final Point size = new Point();
+        final WindowManager wm = getSystemService(WindowManager.class);
+        wm.getDefaultDisplay().getSize(size);
+
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                type, FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
+        params.width = size.x / 3;
+        params.height = size.y / 3;
+        params.gravity = TOP | LEFT;
+        params.setTitle(windowName);
+
+        final TextView view = new TextView(this);
+        view.setText(windowName + "   type=" + type);
+        view.setBackgroundColor(Color.RED);
+        wm.addView(view, params);
+    }
+
+    private boolean isSystemAlertWindowType(int type) {
+        final int[] alertWindowTypes = getAlertWindowTypes();
+        for (int current : alertWindowTypes) {
+            if (current == type) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected abstract int[] getAlertWindowTypes();
+}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java
new file mode 100644
index 0000000..84eabf2
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java
@@ -0,0 +1,166 @@
+/*
+ * 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.server.cts;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsWindowManagerHostTestCases android.server.cts.AlertWindowsTests
+ */
+public class AlertWindowsTests extends ActivityManagerTestBase {
+
+    private static final String PACKAGE_NAME = "android.server.alertwindowapp";
+    private static final String ACTIVITY_NAME = "AlertWindowTestActivity";
+    private static final String SDK_25_PACKAGE_NAME = "android.server.alertwindowappsdk25";
+    private static final String SDK_25_ACTIVITY_NAME = "AlertWindowTestActivitySdk25";
+
+    // From WindowManager.java
+    private static final int TYPE_BASE_APPLICATION      = 1;
+    private static final int FIRST_SYSTEM_WINDOW        = 2000;
+
+    private static final int TYPE_PHONE                 = FIRST_SYSTEM_WINDOW + 2;
+    private static final int TYPE_SYSTEM_ALERT          = FIRST_SYSTEM_WINDOW + 3;
+    private static final int TYPE_SYSTEM_OVERLAY        = FIRST_SYSTEM_WINDOW + 6;
+    private static final int TYPE_PRIORITY_PHONE        = FIRST_SYSTEM_WINDOW + 7;
+    private static final int TYPE_SYSTEM_ERROR          = FIRST_SYSTEM_WINDOW + 10;
+    private static final int TYPE_APPLICATION_OVERLAY   = FIRST_SYSTEM_WINDOW + 38;
+
+    private static final int TYPE_STATUS_BAR            = FIRST_SYSTEM_WINDOW;
+    private static final int TYPE_INPUT_METHOD          = FIRST_SYSTEM_WINDOW + 11;
+    private static final int TYPE_NAVIGATION_BAR        = FIRST_SYSTEM_WINDOW + 19;
+
+    private final List<Integer> mAlertWindowTypes = Arrays.asList(
+            TYPE_PHONE,
+            TYPE_PRIORITY_PHONE,
+            TYPE_SYSTEM_ALERT,
+            TYPE_SYSTEM_ERROR,
+            TYPE_SYSTEM_OVERLAY,
+            TYPE_APPLICATION_OVERLAY);
+    private final List<Integer> mSystemWindowTypes = Arrays.asList(
+            TYPE_STATUS_BAR,
+            TYPE_INPUT_METHOD,
+            TYPE_NAVIGATION_BAR);
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        try {
+            setAlertWindowPermission(PACKAGE_NAME, false);
+            setAlertWindowPermission(SDK_25_PACKAGE_NAME, false);
+            executeShellCommand("am force-stop " + PACKAGE_NAME);
+            executeShellCommand("am force-stop " + SDK_25_PACKAGE_NAME);
+        } catch (DeviceNotAvailableException e) {
+        }
+    }
+
+    public void testAlertWindowAllowed() throws Exception {
+        runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, true /* hasAlertWindowPermission */,
+                true /* atLeastO */);
+    }
+
+    public void testAlertWindowDisallowed() throws Exception {
+        runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, false /* hasAlertWindowPermission */,
+                true /* atLeastO */);
+    }
+
+    public void testAlertWindowAllowedSdk25() throws Exception {
+        runAlertWindowTest(SDK_25_PACKAGE_NAME, SDK_25_ACTIVITY_NAME,
+                true /* hasAlertWindowPermission */, false /* atLeastO */);
+    }
+
+    public void testAlertWindowDisallowedSdk25() throws Exception {
+        runAlertWindowTest(SDK_25_PACKAGE_NAME, SDK_25_ACTIVITY_NAME,
+                false /* hasAlertWindowPermission */, false /* atLeastO */);
+    }
+
+    private void runAlertWindowTest(String packageName, String activityName,
+            boolean hasAlertWindowPermission, boolean atLeastO) throws Exception {
+        setComponentName(packageName);
+        setAlertWindowPermission(packageName, hasAlertWindowPermission);
+
+        executeShellCommand(getAmStartCmd(activityName));
+        mAmWmState.computeState(mDevice, new String[] { activityName });
+        mAmWmState.assertVisibility(activityName, true);
+
+        assertAlertWindows(packageName, hasAlertWindowPermission, atLeastO);
+    }
+
+    private void assertAlertWindows(String packageName, boolean hasAlertWindowPermission,
+            boolean atLeastO) {
+        final WindowManagerState wMState = mAmWmState.getWmState();
+
+        final ArrayList<WindowManagerState.WindowState> alertWindows = new ArrayList();
+        wMState.getWindowsByPackageName(packageName, mAlertWindowTypes, alertWindows);
+
+        if (!hasAlertWindowPermission) {
+            assertTrue("Should be empty alertWindows=" + alertWindows, alertWindows.isEmpty());
+            return;
+        }
+
+        if (atLeastO) {
+            // Assert that only TYPE_APPLICATION_OVERLAY was created.
+            for (WindowManagerState.WindowState win : alertWindows) {
+                assertTrue("Can't create win=" + win + " on SDK O or greater",
+                        win.getType() == TYPE_APPLICATION_OVERLAY);
+            }
+        }
+
+        final WindowManagerState.WindowState mainAppWindow =
+                wMState.getWindowByPackageName(packageName, TYPE_BASE_APPLICATION);
+
+        assertNotNull(mainAppWindow);
+
+        wMState.sortWindowsByLayer(alertWindows);
+        final WindowManagerState.WindowState lowestAlertWindow = alertWindows.get(0);
+        final WindowManagerState.WindowState highestAlertWindow =
+                alertWindows.get(alertWindows.size() - 1);
+
+        // Assert that the alert windows have higher z-order than the main app window
+        assertTrue("lowestAlertWindow=" + lowestAlertWindow + " less than mainAppWindow="
+                        + mainAppWindow, lowestAlertWindow.getLayer() > mainAppWindow.getLayer());
+
+        // Assert that legacy alert windows have a lower z-order than the new alert window layer.
+        final WindowManagerState.WindowState appOverlayWindow =
+                wMState.getWindowByPackageName(packageName, TYPE_APPLICATION_OVERLAY);
+        if (appOverlayWindow != null && highestAlertWindow != appOverlayWindow) {
+            assertTrue("highestAlertWindow=" + highestAlertWindow
+                    + " greater than appOverlayWindow=" + appOverlayWindow,
+                    highestAlertWindow.getLayer() < appOverlayWindow.getLayer());
+        }
+
+        // Assert that alert windows are below key system windows.
+        final ArrayList<WindowManagerState.WindowState> systemWindows = new ArrayList();
+        wMState.getWindowsByPackageName(packageName, mSystemWindowTypes, systemWindows);
+        if (!systemWindows.isEmpty()) {
+            wMState.sortWindowsByLayer(systemWindows);
+            final WindowManagerState.WindowState lowestSystemWindow = alertWindows.get(0);
+            assertTrue("highestAlertWindow=" + highestAlertWindow
+                    + " greater than lowestSystemWindow=" + lowestSystemWindow,
+                    highestAlertWindow.getLayer() < lowestSystemWindow.getLayer());
+        }
+    }
+
+    private void setAlertWindowPermission(String packageName, boolean allow) throws Exception {
+        executeShellCommand("appops set " + packageName + " android:system_alert_window "
+                + (allow ? "allow" : "deny"));
+    }
+}
diff --git a/hostsidetests/sustainedperf/src/android/SustainedPerformance/cts/SustainedPerformanceHostTest.java b/hostsidetests/sustainedperf/src/android/SustainedPerformance/cts/SustainedPerformanceHostTest.java
index fffeeb8..702b15bb 100644
--- a/hostsidetests/sustainedperf/src/android/SustainedPerformance/cts/SustainedPerformanceHostTest.java
+++ b/hostsidetests/sustainedperf/src/android/SustainedPerformance/cts/SustainedPerformanceHostTest.java
@@ -219,12 +219,21 @@
         device.executeShellCommand("settings put global airplane_mode_on 0");
         device.executeShellCommand("am broadcast -a android.intent.action.AIRPLANE_MODE --ez state false");
 
+        double resDhry = dhrystoneResultsWithMode.get(2);
+        double resApp = appResultsWithMode.get(2);
+
+        /* Report if performance is below 5% margin for both dhrystone and shader */
+        if ((resDhry > 5) || (resApp > 5)) {
+            Log.w("SustainedPerformanceHostTests",
+                  "Sustainable mode results, Dhrystone: " + resDhry + " App: " + resApp);
+        }
+
         /*
-         * Checks if the performance in the mode is consistent with
+         * Error if the performance in the mode is not consistent with
          * 5% error margin for shader and 10% error margin for dhrystone.
          */
         assertFalse("Results in the mode are not sustainable",
-                (dhrystoneResultsWithMode.get(2) > 10) ||
-                (appResultsWithMode.get(2)) > 5);
+                    (resDhry > 15) ||
+                    (resApp > 5));
     }
 }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 413ff7d..1dfbf94 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -28,6 +28,7 @@
 import android.view.accessibility.cts.R;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -36,9 +37,12 @@
 public class AccessibilityNodeInfoTest extends AndroidTestCase {
 
     /** The number of properties of the {@link AccessibilityNodeInfo} class that are marshalled. */
-    private static final int NUM_MARSHALLED_PROPERTIES = 32;
+    private static final int NUM_MARSHALLED_PROPERTIES = 33;
 
-    /** The number of properties that are purposely not marshalled */
+    /**
+     * The number of properties that are purposely not marshalled
+     * mOriginalText - Used when resolving clickable spans; intentionally not parceled
+     */
     private static final int NUM_NONMARSHALLED_PROPERTIES = 1;
 
     @SmallTest
@@ -227,6 +231,8 @@
         info.setLabelFor(new View(getContext()));
         info.setViewIdResourceName("foo.bar:id/baz");
         info.setDrawingOrder(5);
+        info.setAvailableExtraData(
+                Arrays.asList(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
     }
 
     /**
@@ -296,6 +302,8 @@
                 receivedInfo.getViewIdResourceName());
         assertEquals("drawing order has incorrect value", expectedInfo.getDrawingOrder(),
                 receivedInfo.getDrawingOrder());
+        assertEquals("Extra data flags have incorrect value", expectedInfo.getAvailableExtraData(),
+                receivedInfo.getAvailableExtraData());
     }
 
     /**
@@ -333,5 +341,6 @@
                 info.getMovementGranularities());
         assertNull("viewId not properly recycled", info.getViewIdResourceName());
         assertEquals(0, info.getDrawingOrder());
+        assertTrue(info.getAvailableExtraData().isEmpty());
     }
 }
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index e13cdee..f996c8b 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -20,6 +20,7 @@
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
 
     <application android:theme="@android:style/Theme.Holo.NoActionBar">
 
@@ -95,6 +96,32 @@
                 android:resource="@xml/stub_magnification_a11y_service" />
         </service>
 
+        <service
+            android:name=".StubFingerprintGestureService"
+            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService" />
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+            </intent-filter>
+
+            <meta-data
+                    android:name="android.accessibilityservice"
+                    android:resource="@xml/stub_fingerprint_gesture_service" />
+        </service>
+
+        <service
+            android:name=".StubAccessibilityButtonService"
+            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService" />
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.accessibilityservice"
+                android:resource="@xml/stub_accessibility_button_service" />
+        </service>
+
     </application>
 
     <instrumentation
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 7c9547c..3a6b855 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -141,6 +141,10 @@
 
     <string name="stub_gesture_a11y_service_description">com.android.accessibilityservice.cts.StubGestureAccessibilityService</string>
 
+    <string name="stub_fprint_a11y_service_description">com.android.accessibilityservice.cts.StubFingerprintGestureAccessibilityService</string>
+
+    <string name="stub_accessibility_button_service_description">com.android.accessibilityservice.cts.StubAccessibilityButtonService</string>
+
     <string name="android_wiki_paragraphs">
         \n\nAndroid is a\n\n It is developed\n The
         unveiling\n\n</string>
diff --git a/tests/accessibilityservice/res/xml/stub_accessibility_button_service.xml b/tests/accessibilityservice/res/xml/stub_accessibility_button_service.xml
new file mode 100644
index 0000000..cbf6e7e
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_accessibility_button_service.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+                       android:description="@string/stub_accessibility_button_service_description"
+                       android:accessibilityEventTypes="typeAllMask"
+                       android:accessibilityFeedbackType="feedbackGeneric"
+                       android:accessibilityFlags="flagRequestAccessibilityButton"
+                       android:notificationTimeout="0" />
\ No newline at end of file
diff --git a/tests/accessibilityservice/res/xml/stub_fingerprint_gesture_service.xml b/tests/accessibilityservice/res/xml/stub_fingerprint_gesture_service.xml
new file mode 100644
index 0000000..c88b8cd
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_fingerprint_gesture_service.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+                       android:description="@string/stub_fprint_a11y_service_description"
+                       android:accessibilityEventTypes="typeAllMask"
+                       android:accessibilityFeedbackType="feedbackGeneric"
+                       android:accessibilityFlags="flagCaptureFingerprintGestures"
+                       android:canCaptureFingerprintGestures="true"
+                       android:notificationTimeout="0" />
\ No newline at end of file
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
new file mode 100644
index 0000000..9a32d3d
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
@@ -0,0 +1,68 @@
+/**
+ * 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.accessibilityservice.cts;
+
+import android.accessibilityservice.AccessibilityButtonController;
+import android.app.Instrumentation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test cases for accessibility service APIs related to the accessibility button within
+ * software-rendered navigation bars.
+ *
+ * TODO: Extend coverage with a more precise signal if a device is compatible with the button
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityButtonTest {
+
+    private StubAccessibilityButtonService mService;
+    private AccessibilityButtonController mButtonController;
+    private AccessibilityButtonController.AccessibilityButtonCallback mStubCallback =
+            new AccessibilityButtonController.AccessibilityButtonCallback() {
+        @Override
+        public void onClicked(AccessibilityButtonController controller) {
+            /* do nothing */
+        }
+
+        @Override
+        public void onAvailabilityChanged(AccessibilityButtonController controller,
+                boolean available) {
+            /* do nothing */
+        }
+    };
+
+    @Before
+    public void setUp() {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        mService = StubAccessibilityButtonService.enableSelf(instrumentation);
+        mButtonController = mService.getAccessibilityButtonController();
+    }
+
+    @After
+    public void tearDown() {
+        mService.runOnServiceSync(() -> mService.disableSelf());
+    }
+
+    @Test
+    public void testCallbackRegistrationUnregistration_serviceDoesNotCrash() {
+        mButtonController.registerAccessibilityButtonCallback(mStubCallback);
+        mButtonController.unregisterAccessibilityButtonCallback(mStubCallback);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 3caea71..b14b76a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -339,7 +339,7 @@
         try {
             // create the notification to send
             channel.enableVibration(true);
-            channel.setLights(true);
+            channel.enableLights(true);
             channel.setBypassDnd(true);
             notificationManager.createNotificationChannel(channel);
             NotificationChannel created =
@@ -404,7 +404,7 @@
         getInstrumentation()
                 .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                this, InstrumentedAccessibilityService.class);
+                getInstrumentation(), InstrumentedAccessibilityService.class);
         try {
             assertFalse(service.wasOnInterruptCalled());
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
new file mode 100644
index 0000000..07add0b
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
@@ -0,0 +1,116 @@
+/**
+ * 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.accessibilityservice.cts;
+
+import android.accessibilityservice.FingerprintGestureController;
+import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
+import android.app.Instrumentation;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.CancellationSignal;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Verify that a service listening for fingerprint gestures gets called back when apps
+ * use the fingerprint sensor to authenticate.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityFingerprintGestureTest {
+    private static final int FINGERPRINT_CALLBACK_TIMEOUT = 3000;
+
+    boolean mIsHardwareAvailable;
+    FingerprintManager mFingerprintManager;
+    StubFingerprintGestureService mFingerprintGestureService;
+    FingerprintGestureController mFingerprintGestureController;
+    CancellationSignal mCancellationSignal = new CancellationSignal();
+
+    @Mock FingerprintManager.AuthenticationCallback mMockAuthenticationCallback;
+    @Mock FingerprintGestureCallback mMockFingerprintGestureCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        mFingerprintManager = instrumentation.getContext()
+                .getSystemService(FingerprintManager.class);
+        mFingerprintGestureService = StubFingerprintGestureService.enableSelf(instrumentation);
+        mIsHardwareAvailable = (mFingerprintManager == null) ? false :
+                mFingerprintManager.isHardwareDetected();
+        mFingerprintGestureController =
+                mFingerprintGestureService.getFingerprintGestureController();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mFingerprintGestureService.runOnServiceSync(() -> mFingerprintGestureService.disableSelf());
+    }
+
+    @Test
+    public void testGetFingerprintManager_returnsManagerIfFeatureAvailable() {
+        if (!mIsHardwareAvailable) {
+            assertNull(mFingerprintGestureController);
+            return;
+        }
+        assertNotNull(mFingerprintGestureController);
+    }
+
+    @Test
+    public void testGestureDetectionAvailable_initialState_shouldBeAvailable() {
+        if (!mIsHardwareAvailable) {
+            return;
+        }
+        assertTrue(mFingerprintGestureController.isGestureDetectionAvailable());
+    }
+
+    @Test
+    public void testGestureDetectionListener_whenAuthenticationStartsAndStops_calledBack() {
+        if (!mIsHardwareAvailable) {
+            return;
+        }
+        mFingerprintGestureController.registerFingerprintGestureCallback(
+                mMockFingerprintGestureCallback, null);
+        try {
+            mFingerprintManager.authenticate(
+                    null, mCancellationSignal, 0, mMockAuthenticationCallback, null);
+
+            verify(mMockFingerprintGestureCallback, timeout(FINGERPRINT_CALLBACK_TIMEOUT))
+                    .onGestureDetectionAvailabilityChanged(false);
+            assertFalse(mFingerprintGestureController.isGestureDetectionAvailable());
+            reset(mMockFingerprintGestureCallback);
+        } finally {
+            mCancellationSignal.cancel();
+        }
+        verify(mMockFingerprintGestureCallback, timeout(FINGERPRINT_CALLBACK_TIMEOUT))
+                .onGestureDetectionAvailabilityChanged(true);
+        assertTrue(mFingerprintGestureController.isGestureDetectionAvailable());
+        mFingerprintGestureController.unregisterFingerprintGestureCallback(
+                mMockFingerprintGestureCallback);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index 31ccee1..23949f2 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
@@ -105,7 +105,7 @@
             mFullScreenTextView.setOnTouchListener(mMyTouchListener);
         });
 
-        mService = StubGestureAccessibilityService.enableSelf(this);
+        mService = StubGestureAccessibilityService.enableSelf(getInstrumentation());
 
         mMotionEvents.clear();
         mCallback = new MyGestureCallback();
@@ -313,7 +313,7 @@
                 centerY + CLICK_SHIFT_FROM_CENTER_Y - mViewBounds.top);
 
         StubMagnificationAccessibilityService magnificationService =
-                StubMagnificationAccessibilityService.enableSelf(this);
+                StubMagnificationAccessibilityService.enableSelf(getInstrumentation());
         android.accessibilityservice.AccessibilityService.MagnificationController
                 magnificationController = magnificationService.getMagnificationController();
         try {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
index b6dcd9d..fd4ac47 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
@@ -19,6 +19,7 @@
 import android.accessibilityservice.AccessibilityService.MagnificationController;
 import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
 import android.content.res.Resources;
 import android.graphics.Region;
 import android.provider.Settings;
@@ -39,16 +40,18 @@
     public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
             "accessibility_display_magnification_enabled";
     private StubMagnificationAccessibilityService mService;
+    private Instrumentation mInstrumentation;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        ShellCommandBuilder.create(this)
+        ShellCommandBuilder.create(this.getInstrumentation())
                 .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
                 .run();
+        mInstrumentation = getInstrumentation();
         // Starting the service will force the accessibility subsystem to examine its settings, so
         // it will update magnification in the process to disable it.
-        mService = StubMagnificationAccessibilityService.enableSelf(this);
+        mService = StubMagnificationAccessibilityService.enableSelf(mInstrumentation);
     }
 
     @Override
@@ -79,7 +82,7 @@
 
     public void testSetScaleAndCenter() {
         final MagnificationController controller = mService.getMagnificationController();
-        final Resources res = getInstrumentation().getTargetContext().getResources();
+        final Resources res = mInstrumentation.getTargetContext().getResources();
         final DisplayMetrics metrics = res.getDisplayMetrics();
         final float scale = 2.0f;
         final float x = metrics.widthPixels / 4.0f;
@@ -138,7 +141,7 @@
         mService.runOnServiceSync(() -> mService.disableSelf());
         mService = null;
         InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                this, InstrumentedAccessibilityService.class);
+                mInstrumentation, InstrumentedAccessibilityService.class);
         final MagnificationController controller2 = service.getMagnificationController();
         try {
             assertEquals("Magnification must reset when a service dies",
@@ -159,7 +162,7 @@
         mService.runOnServiceSync(() -> mService.disableSelf());
         mService = null;
         InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                this, InstrumentedAccessibilityService.class);
+                mInstrumentation, InstrumentedAccessibilityService.class);
         try {
             final MagnificationController controller = service.getMagnificationController();
             Region magnificationRegion = controller.getMagnificationRegion();
@@ -171,13 +174,13 @@
     }
 
     public void testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty() {
-        ShellCommandBuilder.create(this)
+        ShellCommandBuilder.create(mInstrumentation)
                 .putSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, "1")
                 .run();
         mService.runOnServiceSync(() -> mService.disableSelf());
         mService = null;
         InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                this, InstrumentedAccessibilityService.class);
+                mInstrumentation, InstrumentedAccessibilityService.class);
         try {
             final MagnificationController controller = service.getMagnificationController();
             Region magnificationRegion = controller.getMagnificationRegion();
@@ -185,7 +188,7 @@
                     + "gestures are active", magnificationRegion.isEmpty());
         } finally {
             service.runOnServiceSync(() -> service.disableSelf());
-            ShellCommandBuilder.create(this)
+            ShellCommandBuilder.create(mInstrumentation)
                     .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
                     .run();
         }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
index 58d8355..49c209f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -86,7 +86,7 @@
         getActivity();
 
         mService = InstrumentedAccessibilityService.enableService(
-                this, InstrumentedAccessibilityService.class);
+                getInstrumentation(), InstrumentedAccessibilityService.class);
         mKeyboardController = mService.getSoftKeyboardController();
         mUiAutomation = getInstrumentation()
                 .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
index 4ee54b1..23650a6 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -15,7 +15,10 @@
 package android.accessibilityservice.cts;
 
 import android.app.UiAutomation;
+import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.Debug;
+import android.os.Parcelable;
 import android.text.SpannableString;
 import android.text.Spanned;
 import android.text.TextUtils;
@@ -28,7 +31,13 @@
 
 import android.accessibilityservice.cts.R;
 
+import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.List;
+
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 
 /**
  * Test cases for actions taken on text views.
@@ -172,6 +181,91 @@
         assertOnClickCalled();
     }
 
+
+    public void testTextLocations_textViewShouldProvideWhenRequested() {
+        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        makeTextViewVisibleAndSetText(textView, getString(R.string.a_b));
+
+        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+        List<String> textAvailableExtraData = text.getAvailableExtraData();
+        assertTrue("Text view should offer text location to accessibility",
+                textAvailableExtraData.contains(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
+        assertNull("Text locations should not be populated by default",
+                text.getExtras().get(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
+        Bundle getTextArgs = new Bundle();
+        getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0);
+        getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH,
+                text.getText().length());
+        assertTrue("Refresh failed", text.refreshWithExtraData(
+                AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+        final Parcelable[] parcelables = text.getExtras()
+                .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+        final RectF[] locations = Arrays.copyOf(parcelables, parcelables.length, RectF[].class);
+        assertEquals(text.getText().length(), locations.length);
+        // The text should all be on one line, running left to right
+        for (int i = 0; i < locations.length; i++) {
+            assertEquals(locations[0].top, locations[i].top);
+            assertEquals(locations[0].bottom, locations[i].bottom);
+            assertTrue(locations[i].right > locations[i].left);
+            if (i > 0) {
+                assertTrue(locations[i].left > locations[i-1].left);
+            }
+        }
+    }
+
+    public void testTextLocations_textOutsideOfViewBounds_locationsShouldBeNull() {
+        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+        makeTextViewVisibleAndSetText(editText, getString(R.string.android_wiki));
+
+        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(getString(R.string.android_wiki)).get(0);
+        List<String> textAvailableExtraData = text.getAvailableExtraData();
+        assertTrue("Text view should offer text location to accessibility",
+                textAvailableExtraData.contains(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
+        Bundle getTextArgs = new Bundle();
+        getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0);
+        getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH,
+                text.getText().length());
+        assertTrue("Refresh failed", text.refreshWithExtraData(
+                AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+        Parcelable[] parcelables = text.getExtras()
+                .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+        final RectF[] locationsBeforeScroll = Arrays.copyOf(
+                parcelables, parcelables.length, RectF[].class);
+        assertEquals(text.getText().length(), locationsBeforeScroll.length);
+        // The first character should be visible immediately
+        assertFalse(locationsBeforeScroll[0].isEmpty());
+        // Some of the characters should be off the screen, and thus have empty rects. Find the
+        // break point
+        int firstNullRectIndex = -1;
+        for (int i = 1; i < locationsBeforeScroll.length; i++) {
+            boolean isNull = locationsBeforeScroll[i] == null;
+            if (firstNullRectIndex < 0) {
+                if (isNull) {
+                    firstNullRectIndex = i;
+                }
+            } else {
+                assertTrue(isNull);
+            }
+        }
+
+        // Scroll down one line
+        final float oneLineDownY = locationsBeforeScroll[0].bottom;
+        getInstrumentation().runOnMainSync(() -> editText.scrollTo(0, (int) oneLineDownY + 1));
+
+        assertTrue("Refresh failed", text.refreshWithExtraData(
+                AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+        parcelables = text.getExtras()
+                .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+        final RectF[] locationsAfterScroll = Arrays.copyOf(
+                parcelables, parcelables.length, RectF[].class);
+        // Now the first character should be off the screen
+        assertNull(locationsAfterScroll[0]);
+        // The first character that was off the screen should now be on it
+        assertNotNull(locationsAfterScroll[firstNullRectIndex]);
+    }
+
     private void onClickCallback() {
         synchronized (mClickableSpanCallbackLock) {
             mClickableSpanCalled.set(true);
@@ -204,12 +298,9 @@
     }
 
     private void makeTextViewVisibleAndSetText(final TextView textView, final CharSequence text) {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                textView.setVisibility(View.VISIBLE);
-                textView.setText(text);
-            }
+        getInstrumentation().runOnMainSync(() -> {
+            textView.setVisibility(View.VISIBLE);
+            textView.setText(text);
         });
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index f916812..7cc973f 100755
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -580,6 +580,19 @@
             // Android TV doesn't support the divider window
             return;
         }
+
+        // Get com.android.internal.R.bool.config_supportsSplitScreenMultiWindow
+        try {
+            if (!getInstrumentation().getContext().getResources().getBoolean(
+                    Resources.getSystem().getIdentifier(
+                            "config_supportsSplitScreenMultiWindow", "bool", "android"))) {
+                // Check if split screen multi window is not supported.
+                return;
+            }
+        } catch (Resources.NotFoundException e) {
+            // Do nothing, assume split screen multi window is supported.
+        }
+
         // Get com.android.internal.R.bool.config_supportsMultiWindow
         if (!getInstrumentation().getContext().getResources().getBoolean(
                 Resources.getSystem().getIdentifier("config_supportsMultiWindow", "bool",
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
index 31c78c5..324cf33 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
@@ -2,6 +2,7 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
 import android.content.Context;
 import android.os.Handler;
 import android.os.SystemClock;
@@ -111,9 +112,9 @@
     }
 
     protected static <T extends InstrumentedAccessibilityService> T enableService(
-            InstrumentationTestCase testCase, Class<T> clazz) {
+            Instrumentation instrumentation, Class<T> clazz) {
         final String serviceName = clazz.getSimpleName();
-        final Context context = testCase.getInstrumentation().getContext();
+        final Context context = instrumentation.getContext();
         final String enabledServices = Settings.Secure.getString(
                 context.getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
@@ -128,7 +129,7 @@
         for (AccessibilityServiceInfo serviceInfo : serviceInfos) {
             final String serviceId = serviceInfo.getId();
             if (serviceId.endsWith(serviceName)) {
-                ShellCommandBuilder.create(testCase)
+                ShellCommandBuilder.create(instrumentation)
                         .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                                 enabledServices + COMPONENT_NAME_SEPARATOR + serviceId)
                         .putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1")
@@ -136,7 +137,7 @@
 
                 final T instance = getInstanceForClass(clazz, TIMEOUT_SERVICE_ENABLE);
                 if (instance == null) {
-                    ShellCommandBuilder.create(testCase)
+                    ShellCommandBuilder.create(instrumentation)
                             .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                                     enabledServices)
                             .run();
@@ -173,9 +174,9 @@
         return null;
     }
 
-    public static void disableAllServices(InstrumentationTestCase testCase) {
+    public static void disableAllServices(Instrumentation instrumentation) {
         final Object waitLockForA11yOff = new Object();
-        final Context context = testCase.getInstrumentation().getContext();
+        final Context context = instrumentation.getContext();
         final AccessibilityManager manager =
                 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
         manager.addAccessibilityStateChangeListener(b -> {
@@ -184,7 +185,7 @@
             }
         });
 
-        ShellCommandBuilder.create(testCase)
+        ShellCommandBuilder.create(instrumentation)
                 .deleteSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
                 .deleteSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED)
                 .run();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
index 1ad55fc..823b8d6 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
@@ -16,9 +16,9 @@
 
 package android.accessibilityservice.cts;
 
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.os.ParcelFileDescriptor;
-import android.test.InstrumentationTestCase;
 
 import java.io.BufferedReader;
 import java.io.FileInputStream;
@@ -31,18 +31,18 @@
 public class ShellCommandBuilder {
     private final LinkedList<String> mCommands = new LinkedList<>();
 
-    private final InstrumentationTestCase mTestCase;
+    private final Instrumentation mInstrumentation;
 
-    public static ShellCommandBuilder create(InstrumentationTestCase testCase) {
-        return new ShellCommandBuilder(testCase);
+    public static ShellCommandBuilder create(Instrumentation instrumentation) {
+        return new ShellCommandBuilder(instrumentation);
     }
 
-    private ShellCommandBuilder(InstrumentationTestCase testCase) {
-        mTestCase = testCase;
+    private ShellCommandBuilder(Instrumentation instrumentation) {
+        mInstrumentation = instrumentation;
     }
 
     public void run() {
-        final UiAutomation automation = mTestCase.getInstrumentation().getUiAutomation(
+        final UiAutomation automation = mInstrumentation.getUiAutomation(
                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         for (String command : mCommands) {
             execShellCommand(automation, command);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
new file mode 100644
index 0000000..ad3050a
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
@@ -0,0 +1,27 @@
+/**
+ * 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.accessibilityservice.cts;
+
+import android.app.Instrumentation;
+
+/**
+ * A stub accessibility service to install for testing accessibility button APIs
+ */
+public class StubAccessibilityButtonService extends InstrumentedAccessibilityService {
+    public static StubAccessibilityButtonService enableSelf(Instrumentation instrumentation) {
+        return InstrumentedAccessibilityService.enableService(
+                instrumentation, StubAccessibilityButtonService.class);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
new file mode 100644
index 0000000..10776c8
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
@@ -0,0 +1,27 @@
+/**
+ * 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.accessibilityservice.cts;
+
+import android.app.Instrumentation;
+
+/**
+ * A stub accessibility service to install for testing gesture dispatch
+ */
+public class StubFingerprintGestureService extends InstrumentedAccessibilityService {
+    public static StubFingerprintGestureService enableSelf(Instrumentation instrumentation) {
+        return InstrumentedAccessibilityService.enableService(
+                instrumentation, StubFingerprintGestureService.class);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
index caf5ab3..e1699e8 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
@@ -15,11 +15,8 @@
 package android.accessibilityservice.cts;
 
 import android.accessibilityservice.GestureDescription;
+import android.app.Instrumentation;
 import android.os.Handler;
-import android.test.InstrumentationTestCase;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.io.IOException;
 
 /**
  * A stub accessibility service to install for testing gesture dispatch
@@ -31,8 +28,8 @@
         return dispatchGesture(description, callback, handler);
     }
 
-    public static StubGestureAccessibilityService enableSelf(InstrumentationTestCase test) {
+    public static StubGestureAccessibilityService enableSelf(Instrumentation instrumentation) {
         return InstrumentedAccessibilityService.enableService(
-                test, StubGestureAccessibilityService.class);
+                instrumentation, StubGestureAccessibilityService.class);
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
index e5e1949..669ebda 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
@@ -14,20 +14,16 @@
 
 package android.accessibilityservice.cts;
 
-import android.accessibilityservice.GestureDescription;
-import android.os.Handler;
-import android.test.InstrumentationTestCase;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.io.IOException;
+import android.app.Instrumentation;
 
 /**
  * A stub accessibility service to install for testing gesture dispatch
  */
 public class StubMagnificationAccessibilityService extends InstrumentedAccessibilityService {
 
-    public static StubMagnificationAccessibilityService enableSelf(InstrumentationTestCase test) {
+    public static StubMagnificationAccessibilityService enableSelf(
+            Instrumentation instrumentation) {
         return InstrumentedAccessibilityService.enableService(
-                test, StubMagnificationAccessibilityService.class);
+                instrumentation, StubMagnificationAccessibilityService.class);
     }
 }
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index 0a7216d..1e7cf69 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -27,11 +27,29 @@
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.app.stubs"
-                     android:label="CTS tests of android.app">
+            android:targetPackage="android.app.stubs"
+            android:label="CTS tests of android.app">
         <meta-data android:name="listener"
             android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
 
-</manifest>
+    <instrumentation android:name=".DefaultProcessInstrumentation"
+            android:targetPackage="com.android.cts.launcherapps.simpleapp">
+    </instrumentation>
 
+    <instrumentation android:name=".AltProcessInstrumentation"
+            android:targetPackage="com.android.cts.launcherapps.simpleapp"
+            android:targetProcess="com.android.cts.launcherapps.simpleapp:other">
+    </instrumentation>
+
+    <instrumentation android:name=".WildcardProcessInstrumentation"
+                     android:targetPackage="com.android.cts.launcherapps.simpleapp"
+                     android:targetProcess="*">
+    </instrumentation>
+
+    <instrumentation android:name=".MultiProcessInstrumentation"
+                     android:targetPackage="com.android.cts.launcherapps.simpleapp"
+                     android:targetProcess="com.android.cts.launcherapps.simpleapp:other,com.android.cts.launcherapps.simpleapp">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index 6487bd7..78f5f76 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -19,6 +19,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSimpleApp.apk" />
         <option name="test-file-name" value="CtsAppTestStubs.apk" />
+        <option name="test-file-name" value="CtsAppTestStubsDifferentUid.apk" />
         <option name="test-file-name" value="CtsAppTestCases.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundService.java b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
index d0f63bb..f3bb8d3 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundService.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
@@ -17,6 +17,8 @@
 package android.app.stubs;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
@@ -31,6 +33,7 @@
 
     private static final String TAG = "LocalForegroundService";
     private static final String EXTRA_COMMAND = "LocalForegroundService.command";
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
 
     public static final int COMMAND_START_FOREGROUND = 1;
     public static final int COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION = 2;
@@ -44,6 +47,11 @@
     public void onStart(Intent intent, int startId) {
         super.onStart(intent, startId);
 
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        notificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
+
         Context context = getApplicationContext();
         int command = intent.getIntExtra(EXTRA_COMMAND, -1);
 
@@ -51,10 +59,11 @@
             case COMMAND_START_FOREGROUND:
                 mNotificationId ++;
                 Log.d(TAG, "Starting foreground using notification " + mNotificationId);
-                Notification notification = new Notification.Builder(context)
-                        .setContentTitle(getNotificationTitle(mNotificationId))
-                        .setSmallIcon(R.drawable.black)
-                        .build();
+                Notification notification =
+                        new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
+                                .setContentTitle(getNotificationTitle(mNotificationId))
+                                .setSmallIcon(R.drawable.black)
+                                .build();
                 startForeground(mNotificationId, notification);
                 break;
             case COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION:
diff --git a/tests/app/app2/Android.mk b/tests/app/app2/Android.mk
new file mode 100644
index 0000000..ca1c509
--- /dev/null
+++ b/tests/app/app2/Android.mk
@@ -0,0 +1,35 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+
+LOCAL_SRC_FILES := ../app/src/android/app/stubs/LocalService.java
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsAppTestStubsDifferentUid
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/app2/AndroidManifest.xml b/tests/app/app2/AndroidManifest.xml
new file mode 100644
index 0000000..928771e
--- /dev/null
+++ b/tests/app/app2/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.app2">
+    <application>
+        <service android:name="android.app.stubs.LocalService"
+            android:exported="true"/>
+    </application>
+</manifest>
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 910af71..74b5d6c 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -59,11 +59,13 @@
     private static final String SERVICE_NAME = "android.app.stubs.MockService";
     private static final int WAIT_TIME = 2000;
     // A secondary test activity from another APK.
-    private static final String SIMPLE_PACKAGE_NAME = "com.android.cts.launcherapps.simpleapp";
-    private static final String SIMPLE_ACTIVITY = ".SimpleActivity";
-    private static final String SIMPLE_ACTIVITY_IMMEDIATE_EXIT = ".SimpleActivityImmediateExit";
-    private static final String SIMPLE_ACTIVITY_CHAIN_EXIT = ".SimpleActivityChainExit";
-    private static final String SIMPLE_SERVICE = ".SimpleService";
+    static final String SIMPLE_PACKAGE_NAME = "com.android.cts.launcherapps.simpleapp";
+    static final String SIMPLE_ACTIVITY = ".SimpleActivity";
+    static final String SIMPLE_ACTIVITY_IMMEDIATE_EXIT = ".SimpleActivityImmediateExit";
+    static final String SIMPLE_ACTIVITY_CHAIN_EXIT = ".SimpleActivityChainExit";
+    static final String SIMPLE_SERVICE = ".SimpleService";
+    static final String SIMPLE_RECEIVER = ".SimpleReceiver";
+    static final String SIMPLE_REMOTE_RECEIVER = ".SimpleRemoteReceiver";
     // The action sent back by the SIMPLE_APP after a restart.
     private static final String ACTIVITY_LAUNCHED_ACTION =
             "com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION";
@@ -707,9 +709,6 @@
         }
         conn.unbind(WAIT_TIME);
 
-        //cmd = "am kill " + STUB_PACKAGE_NAME;
-        //result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
-
         // Wait for uid's process to go away.
         uidGoneListener.waitForValue(RunningAppProcessInfo.IMPORTANCE_GONE,
                 RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
@@ -719,6 +718,14 @@
         cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
         result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
 
+        // We don't want to wait for the uid to actually go idle, we can force it now.
+        cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
+        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+        // Make sure app is not yet on whitelist
+        cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
+        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
         // We will use this to monitor when the service is running.
         conn.startMonitoring();
 
@@ -779,10 +786,13 @@
             conn.waitForDisconnect(WAIT_TIME);
 
         } finally {
+            mContext.stopService(serviceIntent);
             conn.stopMonitoring();
 
             cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND allow";
             result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
+            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
 
             am.removeOnUidImportanceListener(uidGoneListener);
             am.removeOnUidImportanceListener(uidForegroundListener);
@@ -791,6 +801,36 @@
         }
     }
 
+    public void testDefaultProcessInstrumentation() throws Exception {
+        String cmd = "am instrument -w android.app.cts/.DefaultProcessInstrumentation";
+        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        assertEquals("INSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + "=true" +
+                "\nINSTRUMENTATION_CODE: -1\n", result);
+    }
+
+    public void testAltProcessInstrumentation() throws Exception {
+        String cmd = "am instrument -w android.app.cts/.AltProcessInstrumentation";
+        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        assertEquals("INSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + ":other=true" +
+                "\nINSTRUMENTATION_CODE: -1\n", result);
+    }
+
+    public void testWildcardProcessInstrumentation() throws Exception {
+        String cmd = "am instrument -w android.app.cts/.WildcardProcessInstrumentation";
+        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        assertEquals("INSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + "=true" +
+                "\nINSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + ":receiver=true" +
+                "\nINSTRUMENTATION_CODE: -1\n", result);
+    }
+
+    public void testMultiProcessInstrumentation() throws Exception {
+        String cmd = "am instrument -w android.app.cts/.MultiProcessInstrumentation";
+        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        assertEquals("INSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + "=true" +
+                "\nINSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + ":other=true" +
+                "\nINSTRUMENTATION_CODE: -1\n", result);
+    }
+
     /**
      * Verify that the TimeTrackingAPI works properly when starting and ending an activity.
      */
diff --git a/tests/app/src/android/app/cts/AltProcessInstrumentation.java b/tests/app/src/android/app/cts/AltProcessInstrumentation.java
new file mode 100644
index 0000000..59cd11c
--- /dev/null
+++ b/tests/app/src/android/app/cts/AltProcessInstrumentation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.app.cts;
+
+public class AltProcessInstrumentation extends BaseProcessInstrumentation {
+    public AltProcessInstrumentation() {
+        super(ActivityManagerTest.SIMPLE_PACKAGE_NAME + ":other",
+                ActivityManagerTest.SIMPLE_PACKAGE_NAME
+                        + ActivityManagerTest.SIMPLE_REMOTE_RECEIVER);
+    }
+}
diff --git a/tests/app/src/android/app/cts/BaseProcessInstrumentation.java b/tests/app/src/android/app/cts/BaseProcessInstrumentation.java
new file mode 100644
index 0000000..e816ac9
--- /dev/null
+++ b/tests/app/src/android/app/cts/BaseProcessInstrumentation.java
@@ -0,0 +1,62 @@
+/*
+ * 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.app.cts;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class BaseProcessInstrumentation extends Instrumentation {
+    final String mMainProc;
+    final String mReceiverClass;
+
+    public BaseProcessInstrumentation(String mainProc, String receiverClass) {
+        mMainProc = mainProc;
+        mReceiverClass = receiverClass;
+    }
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        super.onCreate(arguments);
+        final String proc = getProcessName();
+        //Log.i("xxx", "Instrumentation starting in " + proc);
+        final Bundle result = new Bundle();
+        result.putBoolean(proc, true);
+        if (proc.equals(mMainProc)) {
+            // We are running in the main instr process...  start a service that will launch
+            // a secondary proc.
+            Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setClassName(ActivityManagerTest.SIMPLE_PACKAGE_NAME, mReceiverClass);
+            intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            //Log.i("xxx", "Instrumentation sending broadcast: " + intent);
+            getContext().sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+                @Override public void onReceive(Context context, Intent intent) {
+                    //Log.i("xxx", "Instrumentation finishing in " + proc);
+                    finish(Activity.RESULT_OK, result);
+                }
+            }, null, 0, null, null);
+        } else {
+            // We are running in a secondary proc, just report it.
+            //Log.i("xxx", "Instrumentation adding result in " + proc);
+            addResults(result);
+        }
+    }
+}
diff --git a/tests/app/src/android/app/cts/DefaultProcessInstrumentation.java b/tests/app/src/android/app/cts/DefaultProcessInstrumentation.java
new file mode 100644
index 0000000..ca6503d
--- /dev/null
+++ b/tests/app/src/android/app/cts/DefaultProcessInstrumentation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.app.cts;
+
+public class DefaultProcessInstrumentation extends BaseProcessInstrumentation {
+    public DefaultProcessInstrumentation() {
+        super(ActivityManagerTest.SIMPLE_PACKAGE_NAME,
+                ActivityManagerTest.SIMPLE_PACKAGE_NAME
+                        + ActivityManagerTest.SIMPLE_REMOTE_RECEIVER);
+    }
+}
diff --git a/tests/app/src/android/app/cts/MultiProcessInstrumentation.java b/tests/app/src/android/app/cts/MultiProcessInstrumentation.java
new file mode 100644
index 0000000..4f3af4f
--- /dev/null
+++ b/tests/app/src/android/app/cts/MultiProcessInstrumentation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.app.cts;
+
+public class MultiProcessInstrumentation extends BaseProcessInstrumentation {
+    public MultiProcessInstrumentation() {
+        super(ActivityManagerTest.SIMPLE_PACKAGE_NAME + ":other",
+                ActivityManagerTest.SIMPLE_PACKAGE_NAME
+                        + ActivityManagerTest.SIMPLE_RECEIVER);
+    }
+}
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index 1aeb1c6..a2df4bd 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -16,8 +16,11 @@
 
 package android.app.cts;
 
+import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.graphics.Color;
+import android.media.AudioAttributes;
 import android.net.Uri;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
@@ -47,7 +50,10 @@
         assertEquals(null, channel.getVibrationPattern());
         assertEquals(NotificationManager.IMPORTANCE_DEFAULT, channel.getImportance());
         assertEquals(null, channel.getSound());
-        assertFalse(channel.canShowBadge());
+        assertTrue(channel.canShowBadge());
+        assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, channel.getAudioAttributes());
+        assertEquals(null, channel.getGroup());
+        assertTrue(channel.getLightColor() == 0);
     }
 
     public void testWriteToParcel() {
@@ -63,12 +69,20 @@
     public void testLights() {
         NotificationChannel channel =
                 new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
-        channel.setLights(true);
+        channel.enableLights(true);
         assertTrue(channel.shouldShowLights());
-        channel.setLights(false);
+        channel.enableLights(false);
         assertFalse(channel.shouldShowLights());
     }
 
+    public void testLightColor() {
+        NotificationChannel channel =
+                new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setLightColor(Color.RED);
+        assertFalse(channel.shouldShowLights());
+        assertEquals(Color.RED, channel.getLightColor());
+    }
+
     public void testVibration() {
         NotificationChannel channel =
                 new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
@@ -85,17 +99,28 @@
         assertNull(channel.getVibrationPattern());
         channel.setVibrationPattern(pattern);
         assertEquals(pattern, channel.getVibrationPattern());
-        channel.enableVibration(true);
         assertTrue(channel.shouldVibrate());
+
+        channel.setVibrationPattern(new long[]{});
+        assertEquals(false, channel.shouldVibrate());
+
+        channel.setVibrationPattern(null);
+        assertEquals(false, channel.shouldVibrate());
     }
 
-    public void testRingtone() {
+    public void testSound() {
         Uri expected = new Uri.Builder().scheme("fruit").appendQueryParameter("favorite", "bananas")
                 .build();
+        AudioAttributes attributes = new AudioAttributes.Builder()
+                .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+                .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+                .build();
         NotificationChannel channel =
                 new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
-        channel.setSound(expected);
+        channel.setSound(expected, attributes);
         assertEquals(expected, channel.getSound());
+        assertEquals(attributes, channel.getAudioAttributes());
     }
 
     public void testShowBadge() {
@@ -104,4 +129,11 @@
         channel.setShowBadge(true);
         assertTrue(channel.canShowBadge());
     }
+
+    public void testGroup() {
+        NotificationChannel channel =
+                new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setGroup("banana");
+        assertEquals("banana", channel.getGroup());
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index b19e117..6b48128 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -24,6 +24,7 @@
 import android.app.stubs.R;
 import android.content.Context;
 import android.content.Intent;
+import android.media.AudioAttributes;
 import android.net.Uri;
 import android.provider.Telephony.Threads;
 import android.service.notification.StatusBarNotification;
@@ -77,8 +78,10 @@
                 new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
         channel.enableVibration(true);
         channel.setVibrationPattern(new long[] {5, 8, 2, 1});
-        channel.setSound(new Uri.Builder().scheme("test").build());
-        channel.setLights(true);
+        channel.setSound(new Uri.Builder().scheme("test").build(),
+                new AudioAttributes.Builder().setUsage(
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED).build());
+        channel.enableLights(true);
         channel.setBypassDnd(true);
         channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
         mNotificationManager.createNotificationChannel(channel);
@@ -297,12 +300,13 @@
         final int id = 128;
         final long timeout = 1000;
 
-        final Notification notification = new Notification.Builder(mContext)
-                .setSmallIcon(R.drawable.black)
-                .setContentTitle("notify#" + id)
-                .setContentText("This is #" + id + "notification  ")
-                .setTimeout(System.currentTimeMillis() + timeout)
-                .build();
+        final Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(R.drawable.black)
+                        .setContentTitle("notify#" + id)
+                        .setContentText("This is #" + id + "notification  ")
+                        .setTimeout(System.currentTimeMillis() + timeout)
+                        .build();
         mNotificationManager.notify(id, notification);
 
         if (!checkNotificationExistence(id, /*shouldExist=*/ true)) {
@@ -382,5 +386,6 @@
         assertEquals(expected.getSound(), actual.getSound());
         assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
         assertEquals(expected.getGroup(), actual.getGroup());
+        assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
     }
 }
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 1d608cf..c0877bc 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -18,6 +18,8 @@
 
 import android.app.Notification;
 import android.app.Notification.MessagingStyle.Message;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
 import android.content.Context;
@@ -42,6 +44,10 @@
     private static final String URI_STRING = "uriString";
     private static final String ACTION_TITLE = "actionTitle";
     private static final int TOLERANCE = 200;
+    private static final long TIMEOUT = 4000;
+    private static final NotificationChannel CHANNEL = new NotificationChannel("id", "name",
+            NotificationManager.IMPORTANCE_HIGH);
+    private static final String SHORTCUT_ID = "shortcutId";
 
     @Override
     protected void setUp() throws Exception {
@@ -62,6 +68,15 @@
         assertEquals(notificationTime, mNotification.when);
         assertEquals(0, mNotification.icon);
         assertEquals(TICKER_TEXT, mNotification.tickerText);
+        assertEquals(1, mNotification.number);
+    }
+
+    public void testBuilderConstructor() {
+        mNotification = new Notification.Builder(mContext, CHANNEL.getId()).build();
+        assertEquals(CHANNEL.getId(), mNotification.getChannel());
+        assertEquals(Notification.BADGE_ICON_LARGE, mNotification.getBadgeIcon());
+        assertNull(mNotification.getShortcutId());
+        assertEquals((long) 0, mNotification.getTimeout());
     }
 
     public void testDescribeContents() {
@@ -71,7 +86,12 @@
     }
 
     public void testWriteToParcel() {
-        mNotification = new Notification();
+
+        mNotification = new Notification.Builder(mContext, CHANNEL.getId())
+                .chooseBadgeIcon(Notification.BADGE_ICON_SMALL)
+                .setShortcutId(SHORTCUT_ID)
+                .setTimeout(TIMEOUT)
+                .build();
         mNotification.icon = 0;
         mNotification.number = 1;
         final Intent intent = new Intent();
@@ -97,6 +117,7 @@
         mNotification.ledOnMS = 0;
         mNotification.ledOffMS = 0;
         mNotification.iconLevel = 0;
+
         Parcel parcel = Parcel.obtain();
         mNotification.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -120,6 +141,10 @@
         assertEquals(mNotification.ledOnMS, result.ledOnMS);
         assertEquals(mNotification.ledOffMS, result.ledOffMS);
         assertEquals(mNotification.iconLevel, result.iconLevel);
+        assertEquals(mNotification.getShortcutId(), result.getShortcutId());
+        assertEquals(mNotification.getBadgeIcon(), result.getBadgeIcon());
+        assertEquals(mNotification.getTimeout(), result.getTimeout());
+        assertEquals(mNotification.getChannel(), result.getChannel());
 
         mNotification.contentIntent = null;
         parcel = Parcel.obtain();
@@ -158,7 +183,7 @@
     }
 
     public void testColorizeNotification() {
-        mNotification = new Notification.Builder(mContext)
+        mNotification = new Notification.Builder(mContext, "channel_id")
                 .setSmallIcon(1)
                 .setContentTitle(CONTENT_TITLE)
                 .setColorized(true)
@@ -170,17 +195,23 @@
     public void testBuilder() {
         final Intent intent = new Intent();
         final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
-        mNotification = new Notification.Builder(mContext, "channel_id")
+        mNotification = new Notification.Builder(mContext, CHANNEL.getId())
                 .setSmallIcon(1)
                 .setContentTitle(CONTENT_TITLE)
                 .setContentText(CONTENT_TEXT)
                 .setContentIntent(contentIntent)
+                .chooseBadgeIcon(Notification.BADGE_ICON_NONE)
+                .setShortcutId(SHORTCUT_ID)
+                .setTimeout(TIMEOUT)
                 .build();
         assertEquals(CONTENT_TEXT, mNotification.extras.getString(Notification.EXTRA_TEXT));
         assertEquals(CONTENT_TITLE, mNotification.extras.getString(Notification.EXTRA_TITLE));
         assertEquals(1, mNotification.icon);
         assertEquals(contentIntent, mNotification.contentIntent);
-        assertEquals(mNotification.getChannel(), "channel_id");
+        assertEquals(CHANNEL.getId(), mNotification.getChannel());
+        assertEquals(Notification.BADGE_ICON_NONE, mNotification.getBadgeIcon());
+        assertEquals(SHORTCUT_ID, mNotification.getShortcutId());
+        assertEquals(TIMEOUT, mNotification.getTimeout());
     }
 
     public void testActionBuilder() {
@@ -194,7 +225,7 @@
     }
 
     public void testMessagingStyle_historicMessages() {
-        mNotification = new Notification.Builder(mContext)
+        mNotification = new Notification.Builder(mContext, CHANNEL.getId())
                 .setSmallIcon(1)
                 .setContentTitle(CONTENT_TITLE)
                 .setStyle(new Notification.MessagingStyle("self name")
diff --git a/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java b/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java
index 7189dc1..e2a4aff 100644
--- a/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java
+++ b/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java
@@ -51,7 +51,6 @@
                         pipSupportDisabled = true;
                     }
                     assertTrue(pipSupportDisabled);
-                    assertFalse(mActivity.isInMultiWindowMode());
                     assertFalse(mActivity.isInPictureInPictureMode());
                 }
             });
diff --git a/tests/app/src/android/app/cts/PipNotSupportedActivityTest.java b/tests/app/src/android/app/cts/PipNotSupportedActivityTest.java
index e8d13dc..d33ff4d 100644
--- a/tests/app/src/android/app/cts/PipNotSupportedActivityTest.java
+++ b/tests/app/src/android/app/cts/PipNotSupportedActivityTest.java
@@ -51,7 +51,6 @@
                     pipSupportDisabled = true;
                 }
                 assertTrue(pipSupportDisabled);
-                assertFalse(mActivity.isInMultiWindowMode());
                 assertFalse(mActivity.isInPictureInPictureMode());
             }
         });
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 40c323c..2922238 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -16,7 +16,9 @@
 
 package android.app.cts;
 
+import android.app.ActivityManager;
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.stubs.ActivityTestsBase;
 import android.app.stubs.LocalDeniedService;
@@ -39,8 +41,11 @@
 
 import com.android.compatibility.common.util.IBinderParcelable;
 
+import java.util.List;
+
 public class ServiceTest extends ActivityTestsBase {
     private static final String TAG = "ServiceTest";
+    private static final String NOTIFICATION_CHANNEL_ID = TAG;
     private static final int STATE_START_1 = 0;
     private static final int STATE_START_2 = 1;
     private static final int STATE_START_3 = 2;
@@ -52,6 +57,8 @@
     private static final
         String EXIST_CONN_TO_RECEIVE_SERVICE = "existing connection to receive service";
     private static final String EXIST_CONN_TO_LOSE_SERVICE = "existing connection to lose service";
+    private static final String EXTERNAL_SERVICE_COMPONENT =
+            "com.android.app2/android.app.stubs.LocalService";
     private int mExpectedServiceState;
     private Context mContext;
     private Intent mLocalService;
@@ -60,6 +67,7 @@
     private Intent mLocalGrantedService;
     private Intent mLocalService_ApplicationHasPermission;
     private Intent mLocalService_ApplicationDoesNotHavePermission;
+    private Intent mExternalService;
 
     private IBinder mStateReceiver;
 
@@ -173,8 +181,8 @@
         return notificationManager;
     }
 
-    private void sendNotififcation(int id, String title) {
-        Notification notification = new Notification.Builder(getContext())
+    private void sendNotification(int id, String title) {
+        Notification notification = new Notification.Builder(getContext(), NOTIFICATION_CHANNEL_ID)
             .setContentTitle(title)
             .setSmallIcon(R.drawable.black)
             .build();
@@ -383,6 +391,8 @@
         super.setUp();
         mContext = getContext();
         mLocalService = new Intent(mContext, LocalService.class);
+        mExternalService = new Intent();
+        mExternalService.setComponent(ComponentName.unflattenFromString(EXTERNAL_SERVICE_COMPONENT));
         mLocalForegroundService = new Intent(mContext, LocalForegroundService.class);
         mLocalDeniedService = new Intent(mContext, LocalDeniedService.class);
         mLocalGrantedService = new Intent(mContext, LocalGrantedService.class);
@@ -391,6 +401,19 @@
         mLocalService_ApplicationDoesNotHavePermission = new Intent(
                 LocalService.SERVICE_LOCAL_DENIED, null /*uri*/, mContext, LocalService.class);
         mStateReceiver = new MockBinder();
+        getNotificationManager().createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        getNotificationManager().deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+        mContext.stopService(mLocalService);
+        mContext.stopService(mLocalForegroundService);
+        mContext.stopService(mLocalGrantedService);
+        mContext.stopService(mLocalService_ApplicationHasPermission);
+        mContext.stopService(mExternalService);
     }
 
     private class MockBinder extends Binder {
@@ -461,14 +484,6 @@
         }
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mContext.stopService(mLocalService);
-        mContext.stopService(mLocalForegroundService);
-        mContext.stopService(mLocalGrantedService);
-        mContext.stopService(mLocalService_ApplicationHasPermission);
-    }
 
     public void testLocalStartClass() throws Exception {
         startExpectResult(mLocalService);
@@ -507,7 +522,7 @@
 
             // Sends another notification reusing the same notification id.
             String newTitle = "YODA I AM";
-            sendNotififcation(1, newTitle);
+            sendNotification(1, newTitle);
             assertNotification(1, newTitle);
 
             // Start service as foreground again - it should kill notification #1 and show #2
@@ -581,6 +596,53 @@
         assertNoNotification(2);
     }
 
+    public void testRunningServices() throws Exception {
+        final int maxReturnedServices = 10;
+        final Bundle bundle = new Bundle();
+        bundle.putParcelable(LocalService.REPORT_OBJ_NAME, new IBinderParcelable(mStateReceiver));
+
+        boolean success = false;
+
+        ActivityManager am = mContext.getSystemService(ActivityManager.class);
+
+        // No services should be reported back at the beginning
+        assertEquals(0, am.getRunningServices(maxReturnedServices).size());
+        try {
+            mExpectedServiceState = STATE_START_1;
+            // Start external service.
+            mContext.startService(new Intent(mExternalService).putExtras(bundle));
+            waitForResultOrThrow(DELAY, "external service to start first time");
+
+            // Ensure we can't see service.
+            assertEquals(0, am.getRunningServices(maxReturnedServices).size());
+
+            // Start local service.
+            mContext.startService(new Intent(mLocalService).putExtras(bundle));
+            waitForResultOrThrow(DELAY, "local service to start first time");
+            success = true;
+
+            // Ensure we can see service and it is ours.
+            List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(maxReturnedServices);
+            assertEquals(1, services.size());
+            assertEquals(android.os.Process.myUid(), services.get(0).uid);
+        } finally {
+            if (!success) {
+                mContext.stopService(mLocalService);
+                mContext.stopService(mExternalService);
+            }
+        }
+        mExpectedServiceState = STATE_DESTROY;
+
+        mContext.stopService(mExternalService);
+        waitForResultOrThrow(DELAY, "external service to be destroyed");
+
+        mContext.stopService(mLocalService);
+        waitForResultOrThrow(DELAY, "local service to be destroyed");
+
+        // Once our service has stopped, make sure we can't see any services.
+        assertEquals(0, am.getRunningServices(maxReturnedServices).size());
+    }
+
     @MediumTest
     public void testForegroundService_detachNotificationOnStop() throws Exception {
         String newTitle = null;
@@ -602,7 +664,7 @@
 
             // Sends another notification reusing the same notification id.
             newTitle = "YODA I AM";
-            sendNotififcation(1, newTitle);
+            sendNotification(1, newTitle);
             assertNotification(1, newTitle);
 
             // Start service as foreground again - it should show notification #2..
diff --git a/tests/app/src/android/app/cts/WildcardProcessInstrumentation.java b/tests/app/src/android/app/cts/WildcardProcessInstrumentation.java
new file mode 100644
index 0000000..e854e76
--- /dev/null
+++ b/tests/app/src/android/app/cts/WildcardProcessInstrumentation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.app.cts;
+
+public class WildcardProcessInstrumentation extends BaseProcessInstrumentation {
+    public WildcardProcessInstrumentation() {
+        super(ActivityManagerTest.SIMPLE_PACKAGE_NAME,
+                ActivityManagerTest.SIMPLE_PACKAGE_NAME
+                        + ActivityManagerTest.SIMPLE_REMOTE_RECEIVER);
+    }
+}
diff --git a/tests/autofillservice/res/layout/login_activity.xml b/tests/autofillservice/res/layout/login_activity.xml
index 6a8957da..a3601fc 100644
--- a/tests/autofillservice/res/layout/login_activity.xml
+++ b/tests/autofillservice/res/layout/login_activity.xml
@@ -51,6 +51,7 @@
             android:id="@+id/password"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:text="TopSecret"
             android:inputType="textPassword" />
     </LinearLayout>
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 8a90892..7c412bb 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -16,7 +16,7 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.UI_TIMEOUT_SEC;
+import static android.autofillservice.cts.Helper.UI_TIMEOUT_MS;
 import static android.autofillservice.cts.Helper.runShellCommand;
 import static android.provider.Settings.Secure.AUTO_FILL_SERVICE;
 
@@ -44,7 +44,7 @@
 
     @BeforeClass
     public static void setUiBot() throws Exception {
-        sUiBot = new UiBot(InstrumentationRegistry.getInstrumentation(), UI_TIMEOUT_SEC);
+        sUiBot = new UiBot(InstrumentationRegistry.getInstrumentation(), UI_TIMEOUT_MS);
     }
 
     @AfterClass
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index 14ab6a3..4f41e33 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -83,24 +83,28 @@
      */
     static class CannedDataset {
 
-        final Map<String, AutoFillValue> fields;
+        final Map<String, Field> fields;
+        final String id;
         final String name;
 
         private CannedDataset(Builder builder) {
             fields = builder.mFields;
+            id = builder.mId;
             name = builder.mName;
         }
 
         @Override
         public String toString() {
-            return "CannedDataset: [name=" + name + ", fields=" + fields + "]";
+            return "CannedDataset: [id=" + id + ", name=" + name + ", fields=" + fields + "]";
         }
 
         static class Builder {
-            private final Map<String, AutoFillValue> mFields = new HashMap<>();
+            private final Map<String, Field> mFields = new HashMap<>();
+            private final String mId;
             private final String mName;
 
-            public Builder(String name) {
+            public Builder(String id, String name) {
+                mId = id;
                 mName = name;
             }
 
@@ -108,7 +112,16 @@
              * Sets the canned value of a field based on its {@code resourceId}.
              */
             public Builder setField(String resourceId, AutoFillValue value) {
-                mFields.put(resourceId, value);
+                mFields.put(resourceId, new Field(value));
+                return this;
+            }
+
+            /**
+             * Sets a canned value of a field based on its {@code resourceId}, and asserts its
+             * sanitized.
+             */
+            public Builder setSanitizedField(String resourceId, AutoFillValue value) {
+                mFields.put(resourceId, new Field(value, true));
                 return this;
             }
 
@@ -117,4 +130,23 @@
             }
         }
     }
+
+    static class Field  {
+        final boolean sanitized;
+        final AutoFillValue value;
+
+        Field(AutoFillValue value, boolean sanitized) {
+            this.value = value;
+            this.sanitized = sanitized;
+        }
+
+        Field(AutoFillValue value) {
+            this(value, false);
+        }
+
+        @Override
+        public String toString() {
+            return value + (sanitized ? " (sanitized)" : "");
+        }
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index 341a921..6559150 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -35,9 +35,9 @@
     static final long FILL_TIMEOUT_MS = 2000;
 
     /**
-     * Timeout (in seconds) for UI operations. Typically used by {@link UiBot}.
+     * Timeout (in milliseconds) for UI operations. Typically used by {@link UiBot}.
      */
-    static final int UI_TIMEOUT_SEC = 2;
+    static final int UI_TIMEOUT_MS = 2000;
 
     /**
      * Runs a Shell command, returning a trimmed response.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index c679df2..a14d7ed 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -21,11 +21,13 @@
 import android.app.assist.AssistStructure.ViewNode;
 import android.app.assist.AssistStructure.WindowNode;
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.CannedFillResponse.Field;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.service.autofill.AutoFillService;
 import android.service.autofill.FillCallback;
 import android.service.autofill.SaveCallback;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.autofill.AutoFillId;
 import android.view.autofill.AutoFillValue;
@@ -69,11 +71,13 @@
         final CannedFillResponse cannedResponse = sCannedFillResponse.getAndSet(null);
         Log.v(TAG, "onFillRequest(#" + requestNumber + "): cannedResponse = " + cannedResponse);
 
+        assertWithMessage("CancelationSignal is null").that(cancellationSignal).isNotNull();
+
         if (cannedResponse == null) {
             callback.onSuccess(null);
             return;
         }
-        final FillResponse.Builder responseBuilder = new FillResponse.Builder();
+        final FillResponse.Builder responseBuilder = new FillResponse.Builder("4815162342");
         final List<CannedDataset> datasets = cannedResponse.datasets;
 
         if (datasets.isEmpty()) {
@@ -85,13 +89,13 @@
 
         final CannedDataset dataset = datasets.get(0);
 
-        final Map<String, AutoFillValue> fields = dataset.fields;
+        final Map<String, Field> fields = dataset.fields;
         if (fields.isEmpty()) {
             callback.onSuccess(responseBuilder.build());
             return;
         }
 
-        final Dataset.Builder datasetBuilder = new Dataset.Builder(dataset.name);
+        final Dataset.Builder datasetBuilder = new Dataset.Builder(dataset.id, dataset.name);
 
         Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
         final int nodes = structure.getWindowNodeCount();
@@ -141,15 +145,29 @@
         assertWithMessage("Invalid number of fill requests").that(actual).isEqualTo(expected);
     }
 
-    private void fill(Dataset.Builder builder, Map<String, AutoFillValue> fields,
+    private void fill(Dataset.Builder builder, Map<String, Field> fields,
             ViewNode view) {
         final String resourceId = view.getIdEntry();
+        final Field field = fields.get(resourceId);
 
-        final AutoFillValue value = fields.get(resourceId);
-        if (value != null) {
-            final AutoFillId id = view.getAutoFillId();
-            Log.d(TAG, "setting '" + resourceId + "' (" + id + ") to " + value);
-            builder.setValue(id, value);
+        if (field != null) {
+            // Make sure it's sanitized
+            if (field.sanitized) {
+                final CharSequence text = view.getText();
+                if (!TextUtils.isEmpty(text)) {
+                    throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
+                }
+                final AutoFillValue initialValue = view.getAutoFillValue();
+                assertWithMessage("auto-fill value on sanitized field %s: %s", resourceId,
+                        initialValue).that(initialValue).isNull();
+            }
+
+            final AutoFillValue value = field.value;
+            if (value != null) {
+                final AutoFillId id = view.getAutoFillId();
+                Log.d(TAG, "setting '" + resourceId + "' (" + id + ") to " + value);
+                builder.setValue(id, value);
+            }
         }
 
         final int childrenSize = view.getChildCount();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index 9d94c99..9bc1dd2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -114,14 +114,8 @@
     /**
      * Sets the expectation for an auto-fill request, so it can be asserted through
      * {@link #assertAutoFilled()} later.
-     *
-     * <p>It fills the {@code builder} dataset with the proper fields for {@code} username and
-     * {@code password}, so caller can use it to set a {@link CannedFillResponse}.
      */
-    void expectAutoFill(CannedDataset.Builder builder, String username, String password) {
-        builder
-                .setField(ID_USERNAME, AutoFillValue.forText(username))
-                .setField(ID_PASSWORD, AutoFillValue.forText(password));
+    void expectAutoFill(String username, String password) {
         mAutoFillExpectation = new AutoFillExpectation(username, password);
         mUsernameEditText
                 .addTextChangedListener(new MyTextWatcher(mAutoFillExpectation.usernameLatch));
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index ba8ff95..fe457ca 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -21,47 +21,42 @@
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
+import android.view.autofill.AutoFillValue;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 @SmallTest
 public class LoginActivityTest extends AutoFillServiceTestCase {
 
-    @Rule
+    // TODO(b/33197203): fix logic so it can use @Rule...
+    // Cannot use @Rule because must set service before launching activity
     public final ActivityTestRule<LoginActivity> mActivityRule =
         new ActivityTestRule<LoginActivity>(LoginActivity.class);
 
     private LoginActivity mLoginActivity;
 
-    @Before
-    public void setActivity() {
-        mLoginActivity = mActivityRule.getActivity();
-    }
-
     @Test
     public void testAutoFillOneDataset() throws Exception {
         enableService();
 
-        final CannedDataset.Builder dataset = new CannedDataset.Builder("The Dude");
-        mLoginActivity.expectAutoFill(dataset, "dude", "sweet");
+        final CannedDataset.Builder dataset = new CannedDataset.Builder("4815162342", "The Dude")
+                .setField(ID_USERNAME, AutoFillValue.forText("dude"))
+                .setSanitizedField(ID_PASSWORD, AutoFillValue.forText("sweet"));
 
         InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
                 .addDataset(dataset.build())
                 .build());
 
-        sUiBot.triggerImeByRelativeId(ID_USERNAME);
 
-        // TODO(b/33197203, b/33802548): temporary hack because UI is based on notifications
-        sUiBot.collapseStatusBar();
+        mLoginActivity = mActivityRule.launchActivity(null);
+        mLoginActivity.expectAutoFill("dude", "sweet");
 
+        // TODO(b/33197203): Add this logic back in the test.
         // Make sure tapping on other fields from the dataset does not trigger it again
-        sUiBot.tapByRelativeId(ID_PASSWORD);
-        sUiBot.tapByRelativeId(ID_USERNAME);
-
-        // TODO(b/33197203, b/33802548): temporary hack because UI is based on notifications
-        sUiBot.expandStatusBar();
+        if (false) {
+            sUiBot.tapByRelativeId(ID_PASSWORD);
+            sUiBot.tapByRelativeId(ID_USERNAME);
+        }
 
         sUiBot.selectDataset("The Dude");
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 89e5e02..497644f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -20,8 +20,10 @@
 
 import android.app.Instrumentation;
 import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.UiObjectNotFoundException;
 import android.support.test.uiautomator.UiSelector;
 import android.support.test.uiautomator.Until;
@@ -42,18 +44,17 @@
         mDevice = UiDevice.getInstance(instrumentation);
         mTimeout = timeout;
         mPackageName = instrumentation.getContext().getPackageName();
-
-        collapseStatusBar();
     }
 
     /**
      * Selects an auto-fill dataset whose name should be visible in the UI.
      */
     void selectDataset(String name) {
-        // TODO(b/33197203): use id string when using real auto-fill bar
         Log.v(TAG, "selectDataset(): " + name);
 
-        clickOnNotification(name.toUpperCase());
+        // TODO(b/33197203): Use more qualified ids for UI.
+        final UiObject2 dataset = waitForObject(By.res("android", "text1").text(name));
+        dataset.click();
     }
 
     /**
@@ -82,81 +83,19 @@
         assertWithMessage("Failed to tap object with id '%s'", fullId).that(clicked).isTrue();
     }
 
-    /////////////////////////////////////////////////////////////////////////////////
-    // TODO(b/33197203): temporary code using a notification to request auto-fill. //
-    // Will be removed once UX decide the right way to present it to the user.     //
-    /////////////////////////////////////////////////////////////////////////////////
-    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
-
     /**
-     * Clicks on a UI element.
+     * Waits for and returns an object.
      *
-     * @param uiObject UI element to be clicked.
-     * @param description Elements's description used on logging statements.
+     * @param selector {@link BySelector} that identifies the object.
      */
-    private void click(UiObject uiObject, String description) {
-        try {
-            boolean clicked = uiObject.click();
-            // TODO: assertion below fails sometimes, even though the click succeeded,
-            // (specially when clicking the "Just Once" button), so it's currently just logged.
-            // assertTrue("could not click on object '" + description + "'", clicked);
+    private UiObject2 waitForObject(BySelector selector) {
+        final boolean gotIt = mDevice.wait(Until.hasObject(selector), mTimeout);
+        assertWithMessage("object for '%s' not found in %s ms", selector, mTimeout).that(gotIt)
+                .isTrue();
 
-            Log.v(TAG, "onClick for " + description + ": " + clicked);
-        } catch (UiObjectNotFoundException e) {
-            throw new IllegalStateException("exception when clicking on object '" + description
-                    + "'", e);
-        }
-    }
-
-    /**
-     * Opens the system notification and clicks a given notification.
-     *
-     * @param text Notificaton's text as displayed by the UI.
-     */
-    private void clickOnNotification(String text) {
-        final UiObject notification = getNotification(text);
-        click(notification, "notification '" + text+ "'");
-    }
-
-    private UiObject getNotification(String text) {
-        final boolean opened = mDevice.openNotification();
-        Log.v(TAG, "openNotification(): " + opened);
-        final boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGE)), mTimeout);
-        assertWithMessage("could not get system ui (%s)", SYSTEMUI_PACKAGE).that(gotIt).isTrue();
-
-        return getObject(text);
-    }
-    /**
-     * Gets an object that might not yet be available in current UI.
-     *
-     * @param text Object's text as displayed by the UI.
-     */
-    private UiObject getObject(String text) {
-        final boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
-        assertWithMessage("object with text '%s') not visible yet", text).that(gotIt).isTrue();
-        return getVisibleObject(text);
-    }
-
-    /**
-     * Gets an object which is guaranteed to be present in the current UI.
-     *
-     * @param text Object's text as displayed by the UI.
-     */
-    private UiObject getVisibleObject(String text) {
-        final UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
-        assertWithMessage("could not find object with '%s'", text).that(uiObject.exists()).isTrue();
+        final UiObject2 uiObject = mDevice.findObject(selector);
+        assertWithMessage("object for '%s' null in %s ms", selector, mTimeout).that(uiObject)
+                .isNotNull();
         return uiObject;
     }
-
-    void collapseStatusBar() throws Exception {
-        Helper.runShellCommand("service call statusbar 2");
-    }
-
-    void expandStatusBar() throws Exception {
-        Helper.runShellCommand("service call statusbar 1");
-    }
-
-    /////////////////////////////////////////
-    // End of temporary notification code. //
-    /////////////////////////////////////////
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index 2357f54..84f5ddc 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -350,27 +350,25 @@
                 String.format("Surfaces returned from getSurfaces() don't match those passed in"),
                 previewSurfaces.equals(inputSurfaces));
 
-        // Verify that outputConfiguration throws exception if 2 surfaces are different size
+        // Verify that createCaptureSession fails if 2 surfaces are different size
         SurfaceTexture outputTexture2 = new SurfaceTexture(/* random texture ID*/ 5);
         outputTexture2.setDefaultBufferSize(previewSize.getWidth()/2,
                 previewSize.getHeight()/2);
         Surface outputSurface2 = new Surface(outputTexture2);
-        try {
-            OutputConfiguration configuration = new OutputConfiguration(
-                    OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
-            configuration.enableSurfaceSharing();
-            configuration.addSurface(outputSurface2);
-            fail("No error for invalid output config created from different sizes of surfaces");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
+        OutputConfiguration configuration = new OutputConfiguration(
+                OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
+        configuration.enableSurfaceSharing();
+        configuration.addSurface(outputSurface2);
+        List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+        outputConfigurations.add(configuration);
+        verifyCreateSessionWithConfigsFailure(cameraId, outputConfigurations);
 
         // Verify that outputConfiguration throws exception if 2 surfaces are different format
         ImageReader imageReader = makeImageReader(previewSize, ImageFormat.YUV_420_888,
                 MAX_READER_IMAGES, new ImageDropperListener(), mHandler);
         try {
-            OutputConfiguration configuration = new OutputConfiguration(
-                    OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
+            configuration = new OutputConfiguration(OutputConfiguration.SURFACE_GROUP_ID_NONE,
+                    surfaces[0]);
             configuration.enableSurfaceSharing();
             configuration.addSurface(imageReader.getSurface());
             fail("No error for invalid output config created from different format surfaces");
@@ -392,8 +390,7 @@
         // Verify that outputConfiguration throws exception if deferred surface and non-deferred
         // surface properties don't match
         try {
-            OutputConfiguration configuration = new OutputConfiguration(
-                previewSize, SurfaceTexture.class);
+            configuration = new OutputConfiguration(previewSize, SurfaceTexture.class);
             configuration.addSurface(imageReader.getSurface());
             fail("No error for invalid output config created deferred class with different type");
         } catch (IllegalArgumentException e) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index 2538645..2ae9578 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -503,6 +503,17 @@
             }
         }
 
+        // Confirm that zero surface size isn't supported for OutputConfiguration
+        Size[] sizeZeros = { new Size(0, 0), new Size(1, 0), new Size(0, 1) };
+        for (Size size : sizeZeros) {
+            try {
+                OutputConfiguration bad = new OutputConfiguration(size, SurfaceHolder.class);
+                fail("OutputConfiguration allowed use of zero surfaceSize");
+            } catch (IllegalArgumentException e) {
+                //expected
+            }
+        }
+
         // Create session
 
         BlockingSessionCallback sessionListener =
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index a8180d1..a1d63d8 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -52,6 +52,7 @@
 
 import junit.framework.Assert;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.HashMap;
 
@@ -279,6 +280,12 @@
         return camera.getOrderedPreviewSizes();
     }
 
+    protected void verifyCreateSessionWithConfigsFailure(String cameraId,
+            List<OutputConfiguration> configs) throws Exception {
+        CameraHolder camera = getCameraHolder(cameraId);
+        camera.verifyCreateSessionWithConfigsFailure(configs);
+    }
+
     /**
      * Wait until the SurfaceTexture available from the TextureView, then return it.
      * Return null if the wait times out.
@@ -436,6 +443,20 @@
             mSession = configureCameraSessionWithConfig(mCamera, outputConfigs, mSessionListener, mHandler);
         }
 
+        public void verifyCreateSessionWithConfigsFailure(List<OutputConfiguration> configs)
+                throws Exception {
+            BlockingSessionCallback sessionListener = new BlockingSessionCallback();
+            CameraCaptureSession session = configureCameraSessionWithConfig(
+                    mCamera, configs, sessionListener, mHandler);
+
+            Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
+                    BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
+            int state = sessionListener.getStateWaiter().waitForAnyOfStates(
+                    Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS);
+            assertTrue("Expecting a createSessionWithConfig failure.",
+                    state == BlockingSessionCallback.SESSION_CONFIGURE_FAILED);
+        }
+
         public void startPreviewWithConfigs(List<OutputConfiguration> outputConfigs,
                 CaptureCallback listener)
                 throws Exception {
diff --git a/tests/fragment/src/android/fragment/cts/FragmentLifecycleTest.java b/tests/fragment/src/android/fragment/cts/FragmentLifecycleTest.java
index 4034991..6f049c0 100644
--- a/tests/fragment/src/android/fragment/cts/FragmentLifecycleTest.java
+++ b/tests/fragment/src/android/fragment/cts/FragmentLifecycleTest.java
@@ -441,6 +441,90 @@
         assertFalse(fragment1.mCalledOnResume);
     }
 
+    @Test
+    public void testIsStateSaved() throws Throwable {
+        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
+        FragmentTestUtil.resume(mActivityRule, fc, null);
+        FragmentManager fm = fc.getFragmentManager();
+
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Fragment f = new StrictFragment();
+                fm.beginTransaction()
+                        .add(f, "1")
+                        .commitNow();
+
+                assertFalse("fragment reported state saved while resumed",
+                        f.isStateSaved());
+
+                fc.dispatchPause();
+                fc.saveAllState();
+
+                assertTrue("fragment reported state not saved after saveAllState",
+                        f.isStateSaved());
+
+                fc.dispatchStop();
+
+                assertTrue("fragment reported state not saved after stop",
+                        f.isStateSaved());
+
+                fc.dispatchDestroy();
+
+                assertFalse("fragment reported state saved after destroy",
+                        f.isStateSaved());
+            }
+        });
+    }
+
+    @Test
+    public void testSetArgumentsLifecycle() throws Throwable {
+        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
+        FragmentTestUtil.resume(mActivityRule, fc, null);
+        FragmentManager fm = fc.getFragmentManager();
+
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Fragment f = new StrictFragment();
+                f.setArguments(new Bundle());
+
+                fm.beginTransaction()
+                        .add(f, "1")
+                        .commitNow();
+
+                f.setArguments(new Bundle());
+
+                fc.dispatchPause();
+                fc.saveAllState();
+
+                boolean threw = false;
+                try {
+                    f.setArguments(new Bundle());
+                } catch (IllegalStateException ise) {
+                    threw = true;
+                }
+                assertTrue("fragment allowed setArguments after state save", threw);
+
+                fc.dispatchStop();
+
+                threw = false;
+                try {
+                    f.setArguments(new Bundle());
+                } catch (IllegalStateException ise) {
+                    threw = true;
+                }
+                assertTrue("fragment allowed setArguments after stop", threw);
+
+                fc.dispatchDestroy();
+
+                // Fully destroyed, so fragments have been removed.
+                f.setArguments(new Bundle());
+            }
+        });
+
+    }
+
     /*
      * Test that target fragments are in a useful state when we restore them, even if they're
      * on the back stack.
@@ -596,6 +680,15 @@
         }
     }
 
+    @Test
+    public void targetFragmentSetClear() throws Throwable {
+        final Fragment one = new Fragment();
+        final Fragment two = new Fragment();
+
+        one.setTargetFragment(two, 0);
+        one.setTargetFragment(null, 0);
+    }
+
     private void executePendingTransactions(final FragmentManager fm) throws Throwable {
         mActivityRule.runOnUiThread(new Runnable() {
             @Override
diff --git a/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java b/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
index 3a69b44..5566318 100644
--- a/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
+++ b/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
@@ -17,8 +17,10 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.app.Activity;
 import android.app.Fragment;
 import android.app.FragmentController;
+import android.app.FragmentManager;
 import android.app.FragmentManagerNonConfig;
 import android.app.Instrumentation;
 import android.os.Parcelable;
@@ -40,26 +42,43 @@
         instrumentation.runOnMainSync(() -> {});
     }
 
+    private static void runOnUiThreadRethrow(ActivityTestRule<FragmentTestActivity> rule,
+            Runnable r) {
+        try {
+            rule.runOnUiThread(r);
+        } catch (Throwable t) {
+            throw new RuntimeException(t);
+        }
+    }
+
     public static boolean executePendingTransactions(
             final ActivityTestRule<FragmentTestActivity> rule) {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        return executePendingTransactions(rule, rule.getActivity().getFragmentManager());
+    }
+
+    public static boolean executePendingTransactions(
+            final ActivityTestRule<FragmentTestActivity> rule, final FragmentManager fm) {
         final boolean[] ret = new boolean[1];
-        instrumentation.runOnMainSync(new Runnable() {
+        runOnUiThreadRethrow(rule, new Runnable() {
             @Override
             public void run() {
-                ret[0] = rule.getActivity().getFragmentManager().executePendingTransactions();
+                ret[0] = fm.executePendingTransactions();
             }
         });
         return ret[0];
     }
 
     public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule) {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        return popBackStackImmediate(rule, rule.getActivity().getFragmentManager());
+    }
+
+    public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
+            final FragmentManager fm) {
         final boolean[] ret = new boolean[1];
-        instrumentation.runOnMainSync(new Runnable() {
+        runOnUiThreadRethrow(rule, new Runnable() {
             @Override
             public void run() {
-                ret[0] = rule.getActivity().getFragmentManager().popBackStackImmediate();
+                ret[0] = fm.popBackStackImmediate();
             }
         });
         return ret[0];
@@ -67,12 +86,16 @@
 
     public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
             final int id, final int flags) {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        return popBackStackImmediate(rule, rule.getActivity().getFragmentManager(), id, flags);
+    }
+
+    public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
+            final FragmentManager fm, final int id, final int flags) {
         final boolean[] ret = new boolean[1];
-        instrumentation.runOnMainSync(new Runnable() {
+        runOnUiThreadRethrow(rule, new Runnable() {
             @Override
             public void run() {
-                ret[0] = rule.getActivity().getFragmentManager().popBackStackImmediate(id, flags);
+                ret[0] = fm.popBackStackImmediate(id, flags);
             }
         });
         return ret[0];
@@ -80,12 +103,16 @@
 
     public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
             final String name, final int flags) {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        return popBackStackImmediate(rule, rule.getActivity().getFragmentManager(), name, flags);
+    }
+
+    public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
+            final FragmentManager fm, final String name, final int flags) {
         final boolean[] ret = new boolean[1];
-        instrumentation.runOnMainSync(new Runnable() {
+        runOnUiThreadRethrow(rule, new Runnable() {
             @Override
             public void run() {
-                ret[0] = rule.getActivity().getFragmentManager().popBackStackImmediate(name, flags);
+                ret[0] = fm.popBackStackImmediate(name, flags);
             }
         });
         return ret[0];
@@ -93,11 +120,11 @@
 
     public static void setContentView(final ActivityTestRule<FragmentTestActivity> rule,
             final int layoutId) {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.runOnMainSync(new Runnable() {
+        final Activity activity = rule.getActivity();
+        runOnUiThreadRethrow(rule, new Runnable() {
             @Override
             public void run() {
-                rule.getActivity().setContentView(layoutId);
+                activity.setContentView(layoutId);
             }
         });
     }
diff --git a/tests/fragment/src/android/fragment/cts/FragmentTransactionTest.java b/tests/fragment/src/android/fragment/cts/FragmentTransactionTest.java
new file mode 100644
index 0000000..a25ceb9
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/FragmentTransactionTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.fragment.cts;
+
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests usage of the {@link FragmentTransaction} class.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class FragmentTransactionTest {
+
+    @Rule
+    public ActivityTestRule<FragmentTestActivity> mActivityRule =
+            new ActivityTestRule<>(FragmentTestActivity.class);
+
+    private FragmentTestActivity mActivity;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @Test
+    public void testAddTransactionWithValidFragment() throws Throwable {
+        final Fragment fragment = new CorrectFragment();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getFragmentManager().beginTransaction()
+                        .add(android.R.id.content, fragment)
+                        .addToBackStack(null)
+                        .commit();
+                mActivity.getFragmentManager().executePendingTransactions();
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        assertTrue(fragment.isAdded());
+    }
+
+    @Test
+    public void testAddTransactionWithPrivateFragment() throws Throwable {
+        final Fragment fragment = new PrivateFragment();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                boolean exceptionThrown = false;
+                try {
+                    mActivity.getFragmentManager().beginTransaction()
+                            .add(android.R.id.content, fragment)
+                            .addToBackStack(null)
+                            .commit();
+                    mActivity.getFragmentManager().executePendingTransactions();
+                } catch (IllegalStateException e) {
+                    exceptionThrown = true;
+                } finally {
+                    assertTrue("Exception should be thrown", exceptionThrown);
+                    assertFalse("Fragment shouldn't be added", fragment.isAdded());
+                }
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    @Test
+    public void testAddTransactionWithPackagePrivateFragment() throws Throwable {
+        final Fragment fragment = new PackagePrivateFragment();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                boolean exceptionThrown = false;
+                try {
+                    mActivity.getFragmentManager().beginTransaction()
+                            .add(android.R.id.content, fragment)
+                            .addToBackStack(null)
+                            .commit();
+                    mActivity.getFragmentManager().executePendingTransactions();
+                } catch (IllegalStateException e) {
+                    exceptionThrown = true;
+                } finally {
+                    assertTrue("Exception should be thrown", exceptionThrown);
+                    assertFalse("Fragment shouldn't be added", fragment.isAdded());
+                }
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    @Test
+    public void testAddTransactionWithAnonymousFragment() throws Throwable {
+        final Fragment fragment = new Fragment() {};
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                boolean exceptionThrown = false;
+                try {
+                    mActivity.getFragmentManager().beginTransaction()
+                            .add(android.R.id.content, fragment)
+                            .addToBackStack(null)
+                            .commit();
+                    mActivity.getFragmentManager().executePendingTransactions();
+                } catch (IllegalStateException e) {
+                    exceptionThrown = true;
+                } finally {
+                    assertTrue("Exception should be thrown", exceptionThrown);
+                    assertFalse("Fragment shouldn't be added", fragment.isAdded());
+                }
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    @Test
+    public void testAddTransactionWithNonStaticFragment() throws Throwable {
+        final Fragment fragment = new NonStaticFragment();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                boolean exceptionThrown = false;
+                try {
+                    mActivity.getFragmentManager().beginTransaction()
+                            .add(android.R.id.content, fragment)
+                            .addToBackStack(null)
+                            .commit();
+                    mActivity.getFragmentManager().executePendingTransactions();
+                } catch (IllegalStateException e) {
+                    exceptionThrown = true;
+                } finally {
+                    assertTrue("Exception should be thrown", exceptionThrown);
+                    assertFalse("Fragment shouldn't be added", fragment.isAdded());
+                }
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    @Test
+    public void testPostOnCommit() throws Throwable {
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final boolean[] ran = new boolean[1];
+                FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+                fm.beginTransaction().postOnCommit(new Runnable() {
+                    @Override
+                    public void run() {
+                        ran[0] = true;
+                    }
+                }).commit();
+                fm.executePendingTransactions();
+
+                assertTrue("postOnCommit runnable never ran", ran[0]);
+
+                ran[0] = false;
+
+                boolean threw = false;
+                try {
+                    fm.beginTransaction().postOnCommit(new Runnable() {
+                        @Override
+                        public void run() {
+                            ran[0] = true;
+                        }
+                    }).addToBackStack(null).commit();
+                } catch (IllegalStateException ise) {
+                    threw = true;
+                }
+
+                fm.executePendingTransactions();
+
+                assertTrue("postOnCommit was allowed to be called for back stack transaction",
+                        threw);
+                assertFalse("postOnCommit runnable for back stack transaction was run", ran[0]);
+            }
+        });
+    }
+
+    public static class CorrectFragment extends Fragment {}
+
+    private static class PrivateFragment extends Fragment {}
+
+    static class PackagePrivateFragment extends Fragment {}
+
+    private class NonStaticFragment extends Fragment {}
+}
diff --git a/tests/fragment/src/android/fragment/cts/FragmentViewTests.java b/tests/fragment/src/android/fragment/cts/FragmentViewTests.java
index c12fb30..6f96811 100644
--- a/tests/fragment/src/android/fragment/cts/FragmentViewTests.java
+++ b/tests/fragment/src/android/fragment/cts/FragmentViewTests.java
@@ -999,6 +999,51 @@
         FragmentTestUtil.assertChildren(innerContainer, fragment2);
     }
 
+    // Popping the backstack with non-optimized fragments should execute the operations together.
+    // When a non-backstack fragment will be raised, it should not be destroyed.
+    @Test
+    public void popToNonBackStackFragment() throws Throwable {
+        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
+        final FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+
+        final SimpleViewFragment fragment1 = new SimpleViewFragment();
+
+        fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .commit();
+
+        FragmentTestUtil.executePendingTransactions(mActivityRule);
+
+        final SimpleViewFragment fragment2 = new SimpleViewFragment();
+
+        fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment2)
+                .addToBackStack("two")
+                .commit();
+
+        FragmentTestUtil.executePendingTransactions(mActivityRule);
+
+        final SimpleViewFragment fragment3 = new SimpleViewFragment();
+
+        fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment3)
+                .addToBackStack("three")
+                .commit();
+
+        FragmentTestUtil.executePendingTransactions(mActivityRule);
+
+        FragmentTestUtil.popBackStackImmediate(mActivityRule, "two",
+                FragmentManager.POP_BACK_STACK_INCLUSIVE);
+
+        ViewGroup container = (ViewGroup)
+                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
+
+        FragmentTestUtil.assertChildren(container, fragment1);
+        assertEquals(2, fragment1.onCreateViewCount);
+        assertEquals(1, fragment2.onCreateViewCount);
+        assertEquals(1, fragment3.onCreateViewCount);
+    }
+
     private View findViewById(int viewId) {
         return mActivityRule.getActivity().findViewById(viewId);
     }
@@ -1043,4 +1088,15 @@
             return view;
         }
     }
+
+    public static class SimpleViewFragment extends Fragment {
+        public int onCreateViewCount;
+
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            onCreateViewCount++;
+            return inflater.inflate(R.layout.text_a, container, false);
+        }
+    }
 }
diff --git a/tests/fragment/src/android/fragment/cts/PrimaryNavFragmentTest.java b/tests/fragment/src/android/fragment/cts/PrimaryNavFragmentTest.java
new file mode 100644
index 0000000..c5c11cf
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/PrimaryNavFragmentTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.fragment.cts;
+
+import android.app.FragmentManager;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.MediumTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.fragment.cts.FragmentTestUtil.executePendingTransactions;
+import static android.fragment.cts.FragmentTestUtil.popBackStackImmediate;
+import static junit.framework.TestCase.*;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PrimaryNavFragmentTest {
+    @Rule
+    public ActivityTestRule<FragmentTestActivity> mActivityRule =
+            new ActivityTestRule<>(FragmentTestActivity.class);
+
+    @Test
+    public void delegateBackToPrimaryNav() throws Throwable {
+        final FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+        final StrictFragment strictFragment = new StrictFragment();
+
+        fm.beginTransaction().add(strictFragment, null).setPrimaryNavigationFragment(strictFragment)
+                .commit();
+        executePendingTransactions(mActivityRule, fm);
+
+        assertSame("new fragment is not primary nav fragment", strictFragment,
+                fm.getPrimaryNavigationFragment());
+
+        final StrictFragment child = new StrictFragment();
+        FragmentManager cfm = strictFragment.getChildFragmentManager();
+        cfm.beginTransaction().add(child, null).addToBackStack(null).commit();
+        executePendingTransactions(mActivityRule, cfm);
+
+        assertEquals("child transaction not on back stack", 1, cfm.getBackStackEntryCount());
+
+        // Should execute the pop for the child fragmentmanager
+        assertTrue("popBackStackImmediate returned no action performed",
+                popBackStackImmediate(mActivityRule, fm));
+
+        assertEquals("child transaction still on back stack", 0, cfm.getBackStackEntryCount());
+    }
+
+    @Test
+    public void popPrimaryNav() throws Throwable {
+        final FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+        final StrictFragment strictFragment1 = new StrictFragment();
+
+        fm.beginTransaction().add(strictFragment1, null)
+                .setPrimaryNavigationFragment(strictFragment1)
+                .commit();
+        executePendingTransactions(mActivityRule, fm);
+
+        assertSame("new fragment is not primary nav fragment", strictFragment1,
+                fm.getPrimaryNavigationFragment());
+
+        fm.beginTransaction().remove(strictFragment1).addToBackStack(null).commit();
+        executePendingTransactions(mActivityRule, fm);
+
+        assertNull("primary nav fragment is not null after remove",
+                fm.getPrimaryNavigationFragment());
+
+        popBackStackImmediate(mActivityRule, fm);
+
+        assertSame("primary nav fragment was not restored on pop", strictFragment1,
+                fm.getPrimaryNavigationFragment());
+
+        final StrictFragment strictFragment2 = new StrictFragment();
+        fm.beginTransaction().remove(strictFragment1).add(strictFragment2, null)
+                .setPrimaryNavigationFragment(strictFragment2).addToBackStack(null).commit();
+        executePendingTransactions(mActivityRule, fm);
+
+        assertSame("primary nav fragment not updated to new fragment", strictFragment2,
+                fm.getPrimaryNavigationFragment());
+
+        popBackStackImmediate(mActivityRule, fm);
+
+        assertSame("primary nav fragment not restored on pop", strictFragment1,
+                fm.getPrimaryNavigationFragment());
+
+        fm.beginTransaction().setPrimaryNavigationFragment(strictFragment1)
+                .addToBackStack(null).commit();
+        executePendingTransactions(mActivityRule, fm);
+
+        assertSame("primary nav fragment not retained when set again in new transaction",
+                strictFragment1, fm.getPrimaryNavigationFragment());
+        popBackStackImmediate(mActivityRule, fm);
+
+        assertSame("same primary nav fragment not retained when set primary nav transaction popped",
+                strictFragment1, fm.getPrimaryNavigationFragment());
+    }
+
+    @Test
+    public void replacePrimaryNav() throws Throwable {
+        final FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+        final StrictFragment strictFragment1 = new StrictFragment();
+
+        fm.beginTransaction().add(android.R.id.content, strictFragment1)
+                .setPrimaryNavigationFragment(strictFragment1).commit();
+        executePendingTransactions(mActivityRule, fm);
+
+        assertSame("new fragment is not primary nav fragment", strictFragment1,
+                fm.getPrimaryNavigationFragment());
+
+        final StrictFragment strictFragment2 = new StrictFragment();
+        fm.beginTransaction().replace(android.R.id.content, strictFragment2)
+                .addToBackStack(null).commit();
+
+        executePendingTransactions(mActivityRule, fm);
+
+        assertNull("primary nav fragment not null after replace",
+                fm.getPrimaryNavigationFragment());
+
+        popBackStackImmediate(mActivityRule, fm);
+
+        assertSame("primary nav fragment not restored after popping replace", strictFragment1,
+                fm.getPrimaryNavigationFragment());
+
+        fm.beginTransaction().setPrimaryNavigationFragment(null).commit();
+        executePendingTransactions(mActivityRule, fm);
+
+        assertNull("primary nav fragment not null after explicit set to null",
+                fm.getPrimaryNavigationFragment());
+
+        fm.beginTransaction().replace(android.R.id.content, strictFragment2)
+                .setPrimaryNavigationFragment(strictFragment2).addToBackStack(null).commit();
+        executePendingTransactions(mActivityRule, fm);
+
+        assertSame("primary nav fragment not set correctly after replace", strictFragment2,
+                fm.getPrimaryNavigationFragment());
+
+        popBackStackImmediate(mActivityRule, fm);
+
+        assertNull("primary nav fragment not null after popping replace",
+                fm.getPrimaryNavigationFragment());
+    }
+}
diff --git a/tests/fragment/src/android/fragment/cts/StrictFragment.java b/tests/fragment/src/android/fragment/cts/StrictFragment.java
index f552eaa..67ca91e 100644
--- a/tests/fragment/src/android/fragment/cts/StrictFragment.java
+++ b/tests/fragment/src/android/fragment/cts/StrictFragment.java
@@ -100,8 +100,8 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        if (mCalledOnCreate) {
-            throw new IllegalStateException("onCreate called more than once");
+        if (mCalledOnCreate && !mCalledOnDestroy) {
+            throw new IllegalStateException("onCreate called more than once with no onDestroy");
         }
         mCalledOnCreate = true;
         checkState("onCreate", ATTACHED);
diff --git a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
index 700aff2..ac38171 100644
--- a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
+++ b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
@@ -643,6 +643,24 @@
         });
     }
 
+    /**
+     *
+     * This test verifies that custom ValueAnimators will be start()'ed in a set.
+     */
+    @Test
+    public void testChildAnimatorStartCalled() throws Throwable {
+        MyValueAnimator a1 = new MyValueAnimator();
+        MyValueAnimator a2 = new MyValueAnimator();
+        AnimatorSet set = new AnimatorSet();
+        set.playTogether(a1, a2);
+        mActivityRule.runOnUiThread(() -> {
+            set.start();
+            assertTrue(a1.mStartCalled);
+            assertTrue(a2.mStartCalled);
+        });
+
+    }
+
     static class TargetObj {
         public float value = 0;
 
@@ -668,4 +686,13 @@
             mEndIsCalled = true;
         }
     }
+
+    static class MyValueAnimator extends ValueAnimator {
+        boolean mStartCalled = false;
+        @Override
+        public void start() {
+            // Do not call super intentionally.
+            mStartCalled = true;
+        }
+    }
 }
diff --git a/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
index 4939f3f..d471212 100644
--- a/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
@@ -254,7 +254,7 @@
         anim.addListener(listener);
 
         mActivityRule.runOnUiThread(anim::start);
-        verify(listener, within(500)).onAnimationEnd(anim);
+        verify(listener, within(500)).onAnimationEnd(anim, false);
         // Verify that null target ObjectAnimator didn't get canceled.
         verify(listener, times(0)).onAnimationCancel(anim);
         // Verify that the update listeners gets called a few times.
@@ -389,7 +389,7 @@
         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
         anim.addListener(listener);
         mActivityRule.runOnUiThread(anim::start);
-        verify(listener, within(400)).onAnimationEnd(anim);
+        verify(listener, within(400)).onAnimationEnd(anim, false);
     }
 
     @Test
@@ -508,7 +508,7 @@
         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
         anim.addListener(listener);
         mActivityRule.runOnUiThread(anim::start);
-        verify(listener, within(400)).onAnimationEnd(anim);
+        verify(listener, within(400)).onAnimationEnd(anim, false);
     }
 
     @Test
@@ -609,7 +609,7 @@
         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
         anim.addListener(listener);
         mActivityRule.runOnUiThread(anim::start);
-        verify(listener, within(400)).onAnimationEnd(anim);
+        verify(listener, within(400)).onAnimationEnd(anim, false);
     }
 
     @Test
diff --git a/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java b/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java
index 3cbbbb2..3b827ec 100644
--- a/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java
+++ b/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java
@@ -144,7 +144,7 @@
             throws InterruptedException {
         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
         objectAnimator.addListener(listener);
-        verify(listener, within(timeoutMilliseconds)).onAnimationEnd(objectAnimator);
+        verify(listener, within(timeoutMilliseconds)).onAnimationEnd(objectAnimator, false);
         mInstrumentation.waitForIdleSync();
     }
 
@@ -363,7 +363,7 @@
         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
         anim.addListener(listener);
         mActivityRule.runOnUiThread(anim::start);
-        verify(listener, within(400)).onAnimationEnd(anim);
+        verify(listener, within(400)).onAnimationEnd(anim, false);
     }
 
     @Test
@@ -457,7 +457,7 @@
         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
         anim.addListener(listener);
         mActivityRule.runOnUiThread(anim::start);
-        verify(listener, within(400)).onAnimationEnd(anim);
+        verify(listener, within(400)).onAnimationEnd(anim, false);
     }
 
     @Test
diff --git a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
index 6094362..59b8e5db 100644
--- a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
@@ -23,10 +23,12 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
@@ -134,6 +136,25 @@
         assertEquals(startDelay, mValueAnimator.getStartDelay());
     }
 
+    /**
+     * Verify that an animator with start delay will have its listener's onAnimationStart(...)
+     * and onAnimationEnd(...) called at the right time.
+     */
+    @Test
+    public void testListenerCallbackWithStartDelay() throws Throwable {
+        final ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        anim.setStartDelay(300);
+        anim.setDuration(300);
+        AnimatorListener listener = mock(AnimatorListenerAdapter.class);
+        anim.addListener(listener);
+        mActivityRule.runOnUiThread(() -> {
+            anim.start();
+        });
+
+        verify(listener, timeout(450).times(1)).onAnimationStart(anim, false);
+        verify(listener, timeout(450).times(1)).onAnimationEnd(anim, false);
+    }
+
     @Test
     public void testGetCurrentPlayTime() throws Throwable {
         startAnimation(mValueAnimator);
@@ -195,9 +216,9 @@
         currentPlayTime = delayedAnim.getCurrentPlayTime();
         currentFraction = delayedAnim.getAnimatedFraction();
         currentValue = (Float) delayedAnim.getAnimatedValue();
-        assertEquals(proposedCurrentPlayTime, currentPlayTime);
-        assertEquals(.5f, currentFraction, EPSILON);
-        assertEquals(50, currentValue, EPSILON);
+        assertTrue(currentPlayTime > proposedCurrentPlayTime);
+        assertTrue(currentFraction > 0.5f);
+        assertTrue(currentValue > 50);
 
         mActivityRule.runOnUiThread(delayedAnim::cancel);
     }
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java
index 288a3e9..2396df3 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java
@@ -29,7 +29,7 @@
 import android.transition.Fade;
 import android.transition.Transition;
 import android.transition.Transition.TransitionListener;
-import android.transition.Transition.TransitionListenerAdapter;
+import android.transition.TransitionListenerAdapter;
 import android.view.View;
 
 import java.util.List;
diff --git a/tests/tests/assist/common/src/android/assist/common/Utils.java b/tests/tests/assist/common/src/android/assist/common/Utils.java
index 3f98cb3..362d807 100755
--- a/tests/tests/assist/common/src/android/assist/common/Utils.java
+++ b/tests/tests/assist/common/src/android/assist/common/Utils.java
@@ -53,6 +53,8 @@
     /** Lifecycle Test intent constants */
     public static final String LIFECYCLE_PREFIX = ACTION_PREFIX + "lifecycle_";
     public static final String LIFECYCLE_HASRESUMED = LIFECYCLE_PREFIX + "hasResumed";
+    public static final String LIFECYCLE_HASFOCUS = LIFECYCLE_PREFIX + "hasFocus";
+    public static final String LIFECYCLE_LOSTFOCUS = LIFECYCLE_PREFIX + "lostFocus";
     public static final String LIFECYCLE_ONPAUSE = LIFECYCLE_PREFIX + "onpause";
     public static final String LIFECYCLE_ONSTOP = LIFECYCLE_PREFIX + "onstop";
     public static final String LIFECYCLE_ONDESTROY = LIFECYCLE_PREFIX + "ondestroy";
@@ -84,6 +86,7 @@
     public static final String DISABLE_CONTEXT = "DISABLE_CONTEXT";
     public static final String FLAG_SECURE = "FLAG_SECURE";
     public static final String LIFECYCLE = "LIFECYCLE";
+    public static final String LIFECYCLE_NOUI = "LIFECYCLE_NOUI";
     public static final String SCREENSHOT = "SCREENSHOT";
     public static final String EXTRA_ASSIST = "EXTRA_ASSIST";
     public static final String VERIFY_CONTENT_VIEW = "VERIFY_CONTENT_VIEW";
@@ -95,6 +98,11 @@
     /** Session intent constants */
     public static final String HIDE_SESSION = "android.intent.action.hide_session";
 
+    /** Lifecycle activity intent constants */
+    /** Session intent constants */
+    public static final String HIDE_LIFECYCLE_ACTIVITY
+            = "android.intent.action.hide_lifecycle_activity";
+
     /** Stub html view to load into WebView */
     public static final String WEBVIEW_HTML_GREETING = "Hello WebView!";
     public static final String WEBVIEW_HTML = "<html><body><div><p>" + WEBVIEW_HTML_GREETING
@@ -148,6 +156,7 @@
             case ASSIST_STRUCTURE:
             case FLAG_SECURE:
             case LIFECYCLE:
+            case LIFECYCLE_NOUI:
             case SCREENSHOT:
             case EXTRA_ASSIST:
             case VERIFY_CONTENT_VIEW:
@@ -177,6 +186,7 @@
                 return new ComponentName(
                         "android.assist.testapp", "android.assist.testapp.SecureActivity");
             case LIFECYCLE:
+            case LIFECYCLE_NOUI:
                 return new ComponentName(
                         "android.assist.testapp", "android.assist.testapp.LifecycleActivity");
             case SCREENSHOT:
diff --git a/tests/tests/assist/service/AndroidManifest.xml b/tests/tests/assist/service/AndroidManifest.xml
index 354d771..6f01d3d 100644
--- a/tests/tests/assist/service/AndroidManifest.xml
+++ b/tests/tests/assist/service/AndroidManifest.xml
@@ -44,6 +44,7 @@
           <intent-filter>
               <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE" />
               <action android:name="android.intent.action.START_TEST_LIFECYCLE" />
+              <action android:name="android.intent.action.START_TEST_LIFECYCLE_NOUI" />
               <action android:name="android.intent.action.START_TEST_FLAG_SECURE" />
               <action android:name="android.intent.action.START_TEST_SCREENSHOT" />
               <action android:name="android.intent.action.START_TEST_EXTRA_ASSIST" />
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
index 916d676..708cf9a 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
@@ -43,8 +43,15 @@
     private CountDownLatch mResumeLatch;
 
     @Override
+    public void onCreate() {
+        super.onCreate();
+        Log.i(TAG, "onCreate received");
+    }
+
+    @Override
     public void onReady() {
         super.onReady();
+        Log.i(TAG, "onReady received");
         mReady = true;
     }
 
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
index 7bca9be..0224498 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -92,6 +92,15 @@
     }
 
     @Override
+    public void onPrepareShow(Bundle args, int showFlags) {
+        if (Utils.LIFECYCLE_NOUI.equals(args.getString(Utils.TESTCASE_TYPE, ""))) {
+            setUiEnabled(false);
+        } else  {
+            setUiEnabled(true);
+        }
+    }
+
+    @Override
     public void onShow(Bundle args, int showFlags) {
         if ((showFlags & SHOW_WITH_ASSIST) == 0) {
             return;
diff --git a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
index 9f095aa..0b51314 100644
--- a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
+++ b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
@@ -16,22 +16,12 @@
 
 package android.assist.cts;
 
-import com.android.compatibility.common.util.SystemUtil;
-
-import android.assist.cts.TestStartActivity;
 import android.assist.common.Utils;
 
-import android.app.Activity;
-import android.app.assist.AssistContent;
-import android.app.assist.AssistStructure;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
 import java.lang.Override;
@@ -43,6 +33,8 @@
 public class LifecycleTest extends AssistTestBase {
     private static final String TAG = "LifecycleTest";
     private static final String action_hasResumed = Utils.LIFECYCLE_HASRESUMED;
+    private static final String action_hasFocus = Utils.LIFECYCLE_HASFOCUS;
+    private static final String action_lostFocus = Utils.LIFECYCLE_LOSTFOCUS;
     private static final String action_onPause = Utils.LIFECYCLE_ONPAUSE;
     private static final String action_onStop = Utils.LIFECYCLE_ONSTOP;
     private static final String action_onDestroy = Utils.LIFECYCLE_ONDESTROY;
@@ -50,39 +42,52 @@
     private static final String TEST_CASE_TYPE = Utils.LIFECYCLE;
 
     private BroadcastReceiver mLifecycleTestBroadcastReceiver;
-    private CountDownLatch mHasResumedLatch = new CountDownLatch(1);
-    private CountDownLatch mActivityLifecycleLatch = new CountDownLatch(1);
-    private CountDownLatch mReadyLatch = new CountDownLatch(1);
+    private CountDownLatch mHasResumedLatch;
+    private CountDownLatch mHasFocusLatch;
+    private CountDownLatch mLostFocusLatch;
+    private CountDownLatch mActivityLifecycleLatch;
+    private CountDownLatch mDestroyLatch;
+    private CountDownLatch mReadyLatch;
+    private boolean mLostFocusIsLifecycle;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        setUpAndRegisterReceiver();
-        startTestActivity(TEST_CASE_TYPE);
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-        if (mLifecycleTestBroadcastReceiver != null) {
-            mContext.unregisterReceiver(mLifecycleTestBroadcastReceiver);
-            mLifecycleTestBroadcastReceiver = null;
-        }
-    }
-
-    private void setUpAndRegisterReceiver() {
-        if (mLifecycleTestBroadcastReceiver != null) {
-            mContext.unregisterReceiver(mLifecycleTestBroadcastReceiver);
-        }
         mLifecycleTestBroadcastReceiver = new LifecycleTestReceiver();
         IntentFilter filter = new IntentFilter();
         filter.addAction(action_hasResumed);
+        filter.addAction(action_hasFocus);
+        filter.addAction(action_lostFocus);
         filter.addAction(action_onPause);
         filter.addAction(action_onStop);
         filter.addAction(action_onDestroy);
         filter.addAction(Utils.ASSIST_RECEIVER_REGISTERED);
         mContext.registerReceiver(mLifecycleTestBroadcastReceiver, filter);
+        mHasResumedLatch = new CountDownLatch(1);
+        mHasFocusLatch = new CountDownLatch(1);
+        mLostFocusLatch = new CountDownLatch(1);
+        mActivityLifecycleLatch = new CountDownLatch(1);
+        mDestroyLatch = new CountDownLatch(1);
+        mReadyLatch = new CountDownLatch(1);
+        mLostFocusIsLifecycle = false;
+        startTestActivity(TEST_CASE_TYPE);
+    }
 
+    @Override
+    public void tearDown() throws Exception {
+        mContext.sendBroadcast(new Intent(Utils.HIDE_LIFECYCLE_ACTIVITY));
+        waitForDestroy();
+        super.tearDown();
+        if (mLifecycleTestBroadcastReceiver != null) {
+            mContext.unregisterReceiver(mLifecycleTestBroadcastReceiver);
+            mLifecycleTestBroadcastReceiver = null;
+        }
+        mHasResumedLatch = null;
+        mHasFocusLatch = null;
+        mLostFocusLatch = null;
+        mActivityLifecycleLatch = null;
+        mDestroyLatch = null;
+        mReadyLatch = null;
     }
 
     private void waitForOnResume() throws Exception {
@@ -92,12 +97,33 @@
         }
     }
 
+    private void waitForHasFocus() throws Exception {
+        Log.i(TAG, "waiting for window focus gain before continuing");
+        if (!mHasFocusLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Activity failed to get focus in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+        }
+    }
+
+    private void waitForLostFocus() throws Exception {
+        Log.i(TAG, "waiting for window focus lost before continuing");
+        if (!mLostFocusLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Activity failed to lose focus in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+        }
+    }
+
     private void waitAndSeeIfLifecycleMethodsAreTriggered() throws Exception {
         if (mActivityLifecycleLatch.await(Utils.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
             fail("One or more lifecycle methods were called after triggering assist");
         }
     }
 
+    private void waitForDestroy() throws Exception {
+        Log.i(TAG, "waiting for activity destroy before continuing");
+        if (!mDestroyLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Activity failed to destroy in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+        }
+    }
+
     public void testLayerDoesNotTriggerLifecycleMethods() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -107,8 +133,32 @@
         waitForAssistantToBeReady(mReadyLatch);
         mTestActivity.start3pApp(Utils.LIFECYCLE);
         waitForOnResume();
+        waitForHasFocus();
         startSession();
         waitForContext();
+        // Since there is no UI, focus should not be lost.  We are counting focus lost as
+        // a lifecycle event in this case.
+        // Do this after waitForContext(), since we don't start looking for context until
+        // calling the above (RACY!!!).
+        waitForLostFocus();
+        waitAndSeeIfLifecycleMethodsAreTriggered();
+    }
+
+    public void testNoUiLayerDoesNotTriggerLifecycleMethods() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            Log.d(TAG, "Not running assist tests on low-RAM device.");
+            return;
+        }
+        mLostFocusIsLifecycle = true;
+        mTestActivity.startTest(Utils.LIFECYCLE_NOUI);
+        waitForAssistantToBeReady(mReadyLatch);
+        mTestActivity.start3pApp(Utils.LIFECYCLE_NOUI);
+        waitForOnResume();
+        waitForHasFocus();
+        startSession();
+        waitForContext();
+        // Do this after waitForContext(), since we don't start looking for context until
+        // calling the above (RACY!!!).
         waitAndSeeIfLifecycleMethodsAreTriggered();
     }
 
@@ -118,12 +168,21 @@
             String action = intent.getAction();
             if (action.equals(action_hasResumed) && mHasResumedLatch != null) {
                 mHasResumedLatch.countDown();
+            } else if (action.equals(action_hasFocus) && mHasFocusLatch != null) {
+                mHasFocusLatch.countDown();
+            } else if (action.equals(action_lostFocus) && mLostFocusLatch != null) {
+                if (mLostFocusIsLifecycle) {
+                    mActivityLifecycleLatch.countDown();
+                } else {
+                    mLostFocusLatch.countDown();
+                }
             } else if (action.equals(action_onPause) && mActivityLifecycleLatch != null) {
                 mActivityLifecycleLatch.countDown();
             } else if (action.equals(action_onStop) && mActivityLifecycleLatch != null) {
                 mActivityLifecycleLatch.countDown();
             } else if (action.equals(action_onDestroy) && mActivityLifecycleLatch != null) {
                 mActivityLifecycleLatch.countDown();
+                mDestroyLatch.countDown();
             } else if (action.equals(Utils.ASSIST_RECEIVER_REGISTERED)) {
                 if (mReadyLatch != null) {
                     mReadyLatch.countDown();
diff --git a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java
index af10f99..0602d3f 100644
--- a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java
@@ -17,17 +17,34 @@
 package android.assist.testapp;
 
 import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Bundle;
 import android.util.Log;
 
 public class LifecycleActivity extends Activity {
     private static final String TAG = "LifecycleActivity";
 
+    private BroadcastReceiver mReceiver;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log.i(TAG, "LifecycleActivity created");
+        mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action.equals("android.intent.action.hide_lifecycle_activity")) {
+                    finish();
+                }
+            }
+        };
+        IntentFilter filter = new IntentFilter();
+        filter.addAction("android.intent.action.hide_lifecycle_activity");
+        registerReceiver(mReceiver, filter);
     }
 
     @Override
@@ -38,6 +55,17 @@
     }
 
     @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        Log.i(TAG, "Activity focus changed: " + hasFocus);
+        if (hasFocus) {
+            sendBroadcast(new Intent("android.intent.action.lifecycle_hasFocus"));
+        } else {
+            sendBroadcast(new Intent("android.intent.action.lifecycle_lostFocus"));
+        }
+    }
+
+    @Override
     protected void onPause() {
         Log.i(TAG, "activity was paused");
         sendBroadcast(new Intent("android.intent.action.lifecycle_onpause"));
@@ -55,6 +83,7 @@
     protected void onDestroy() {
         Log.i(TAG, "activity was destroyed");
         sendBroadcast(new Intent("android.intent.action.lifecycle_ondestroy"));
+        unregisterReceiver(mReceiver);
         super.onDestroy();
     }
 }
diff --git a/tests/tests/bionic/Android.build.copy.libs.mk b/tests/tests/bionic/Android.build.copy.libs.mk
index 6c2d546..545a6b7 100644
--- a/tests/tests/bionic/Android.build.copy.libs.mk
+++ b/tests/tests/bionic/Android.build.copy.libs.mk
@@ -10,11 +10,15 @@
 
 # TODO(dimitry): Can this list be constructed dynamically?
 my_bionic_testlib_files := \
+  cfi_test_helper/cfi_test_helper \
+  cfi_test_helper2/cfi_test_helper2 \
   dt_runpath_a/libtest_dt_runpath_a.so \
   dt_runpath_b_c_x/libtest_dt_runpath_b.so \
   dt_runpath_b_c_x/libtest_dt_runpath_c.so \
   dt_runpath_b_c_x/libtest_dt_runpath_x.so \
   libatest_simple_zip/libatest_simple_zip.so \
+  libcfi-test.so \
+  libcfi-test-bad.so \
   libdlext_test_different_soname.so \
   libdlext_test_fd/libdlext_test_fd.so \
   libdlext_test_norelro.so \
diff --git a/tests/tests/contactsproviderwipe/src/android/provider/cts/contactsproviderwipe/ContactsContract_Wipe.java b/tests/tests/contactsproviderwipe/src/android/provider/cts/contactsproviderwipe/ContactsContract_Wipe.java
index 2bea43f..c52aba6 100644
--- a/tests/tests/contactsproviderwipe/src/android/provider/cts/contactsproviderwipe/ContactsContract_Wipe.java
+++ b/tests/tests/contactsproviderwipe/src/android/provider/cts/contactsproviderwipe/ContactsContract_Wipe.java
@@ -16,6 +16,10 @@
 
 package android.provider.cts.contactsproviderwipe;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.database.ContentObserver;
@@ -25,6 +29,7 @@
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.provider.ContactsContract;
+import android.provider.ContactsContract.Intents;
 import android.provider.ContactsContract.ProviderStatus;
 import android.support.test.InstrumentationRegistry;
 import android.test.AndroidTestCase;
@@ -157,11 +162,11 @@
         assertBigger(newTimestamp, start);
     }
 
-    private void checkDatabaseWipeNotification(Uri notificationUri) throws Exception {
+    public void testDatabaseWipeNotification() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         final AtomicReference<Uri> notifiedUri = new AtomicReference<>();
 
-        getContext().getContentResolver().registerContentObserver(notificationUri,
+        getContext().getContentResolver().registerContentObserver(ProviderStatus.CONTENT_URI,
                 /* notifyForDescendants=*/ false,
                 new ContentObserver(new Handler(Looper.getMainLooper())) {
             @Override
@@ -177,11 +182,26 @@
         assertTrue("Didn't receive content change notification",
                 latch.await(60, TimeUnit.SECONDS));
 
-        assertEquals(notificationUri, notifiedUri.get());
+        assertEquals(ProviderStatus.CONTENT_URI, notifiedUri.get());
     }
 
-    public void testDatabaseWipeNotification() throws Exception {
-        checkDatabaseWipeNotification(ProviderStatus.CONTENT_URI);
-        checkDatabaseWipeNotification(ProviderStatus.STATUS_CHANGE_NOTIFICATION_CONTENT_URI);
+    public void testDatabaseWipeBroadcast() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intents.CONTACTS_DATABASE_CREATED);
+
+        getContext().registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Log.i(TAG, "Received broadcast: " + intent);
+                latch.countDown();
+            }
+        }, filter);
+
+        wipeContactsProvider();
+
+        assertTrue("Didn't receive contacts wipe broadcast",
+                latch.await(60, TimeUnit.SECONDS));
     }
 }
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
index 684e665..caaa669 100644
--- a/tests/tests/content/Android.mk
+++ b/tests/tests/content/Android.mk
@@ -20,6 +20,7 @@
 LOCAL_MODULE_TAGS := optional
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_USE_AAPT2 := true
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
diff --git a/tests/tests/content/res/font/samplefont.ttf b/tests/tests/content/res/font/samplefont.ttf
new file mode 100644
index 0000000..49f1c62
--- /dev/null
+++ b/tests/tests/content/res/font/samplefont.ttf
Binary files differ
diff --git a/tests/tests/content/res/font/samplexmlfont.xml b/tests/tests/content/res/font/samplexmlfont.xml
new file mode 100644
index 0000000..2905c13
--- /dev/null
+++ b/tests/tests/content/res/font/samplexmlfont.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/samplefont" />
+</font-family>
\ No newline at end of file
diff --git a/tests/tests/content/res/values/styles.xml b/tests/tests/content/res/values/styles.xml
index c6e4b1d..d1c30d7 100644
--- a/tests/tests/content/res/values/styles.xml
+++ b/tests/tests/content/res/values/styles.xml
@@ -128,8 +128,8 @@
 
     <style name="TestProgressBar">
         <item name="android:indeterminateOnly">false</item>
-        <item name="android:progressDrawable">?android:drawable/progress_horizontal</item>
-        <item name="android:indeterminateDrawable">?android:drawable/progress_horizontal</item>
+        <item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
+        <item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
         <item name="android:minHeight">20dip</item>
         <item name="android:maxHeight">20dip</item>
         <item name="android:focusable">true</item>
diff --git a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
new file mode 100644
index 0000000..a69a3f8
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.content.cts;
+
+import static org.junit.Assert.fail;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ClipDescriptionTest {
+    @UiThreadTest
+    @Test
+    public void testGetTimestamp() {
+        final ClipboardManager clipboardManager = (ClipboardManager)
+                InstrumentationRegistry.getTargetContext().getSystemService(Context.CLIPBOARD_SERVICE);
+        final long timestampBeforeSet = SystemClock.elapsedRealtime();
+        clipboardManager.setPrimaryClip(ClipData.newPlainText("Dummy text", "Text"));
+        final long timestampAfterSet = SystemClock.elapsedRealtime();
+        final long timestamp = clipboardManager.getPrimaryClipDescription().getTimestamp();
+        if (timestamp < timestampBeforeSet || timestamp > timestampAfterSet) {
+            fail("Value of timestamp is not as expected.\n"
+                    + "timestamp before setting clip: " + timestampBeforeSet + "\n"
+                    + "timestamp after setting clip: " + timestampAfterSet + "\n"
+                    + "actual timestamp: " + timestamp
+                    + "clipdata: " + clipboardManager.getPrimaryClip());
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
index 18b1fd3..917f184 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -31,6 +31,7 @@
 import android.content.res.Resources.NotFoundException;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
@@ -706,4 +707,41 @@
 
         is.close();
     }
+
+    public void testGetFont_invalidResourceId() {
+        try {
+            mResources.getFont(-1);
+            fail("Font resource -1 should not be found.");
+        } catch (NotFoundException e) {
+            //expected
+        }
+    }
+
+    public void testGetFont_fontFile() {
+        Typeface font = mResources.getFont(R.font.samplefont);
+
+        assertNotNull(font);
+        assertNotSame(Typeface.DEFAULT, font);
+    }
+
+    public void testGetFont_xmlFile() {
+        Typeface font = mResources.getFont(R.font.samplexmlfont);
+
+        assertNotNull(font);
+        assertNotSame(Typeface.DEFAULT, font);
+    }
+
+    public void testGetFont_fontFileIsCached() {
+        Typeface font = mResources.getFont(R.font.samplefont);
+        Typeface font2 = mResources.getFont(R.font.samplefont);
+
+        assertEquals(font, font2);
+    }
+
+    public void testGetFont_xmlFileIsCached() {
+        Typeface font = mResources.getFont(R.font.samplexmlfont);
+        Typeface font2 = mResources.getFont(R.font.samplexmlfont);
+
+        assertEquals(font, font2);
+    }
 }
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
index f508857..28e9236 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
index fc383fb..b27bdbd 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
index 7afe0da..c9677a6 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
index 801573c..8882a7a 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
index 899a235..143ce3e 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
index ba6d8c7..cdae7fa 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
index c57ad20e..2bf7882 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
index 4916ffa..15a3a43 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
@@ -900,6 +900,21 @@
     }
 
     @Test
+    public void testSetGetFontVariationSettings() {
+        Paint p = new Paint();
+
+        // The default variation settings should be null.
+        assertNull(p.getFontVariationSettings());
+
+        final String settings = "'wdth' 1.0";
+        p.setFontVariationSettings(settings);
+        assertEquals(settings, p.getFontVariationSettings());
+
+        p.setFontVariationSettings("");
+        assertNull(p.getFontVariationSettings());
+    }
+
+    @Test
     public void testGetTextBounds() {
         Paint p = new Paint();
         p.setTextSize(10);
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
index 531549e..21e9086 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
@@ -59,6 +59,7 @@
     private PackageManager mPm;
     private FeatureInfo mVulkanHardwareLevel = null;
     private FeatureInfo mVulkanHardwareVersion = null;
+    private FeatureInfo mVulkanHardwareCompute = null;
     private JSONObject mVulkanDevices[];
 
     @Before
@@ -77,6 +78,11 @@
                     if (DEBUG) {
                         Log.d(TAG, feature.name + "=0x" + Integer.toHexString(feature.version));
                     }
+                } else if (PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE.equals(feature.name)) {
+                    mVulkanHardwareCompute = feature;
+                    if (DEBUG) {
+                        Log.d(TAG, feature.name + "=" + feature.version);
+                    }
                 }
             }
         }
@@ -96,6 +102,9 @@
             assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION +
                        " is supported, but no Vulkan physical devices are available",
                        mVulkanHardwareLevel);
+            assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE +
+                       " is supported, but no Vulkan physical devices are available",
+                       mVulkanHardwareCompute);
             return;
         }
         assertNotNull("Vulkan physical devices are available, but system feature " +
@@ -116,20 +125,31 @@
                    " version 0x" + Integer.toHexString(mVulkanHardwareVersion.version) + " is not" +
                    " one of the versions allowed",
                    isHardwareVersionAllowed(mVulkanHardwareVersion.version));
+        if (mVulkanHardwareCompute != null) {
+            assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE +
+                       " version " + mVulkanHardwareCompute.version +
+                       " is not one of the versions allowed",
+                       mVulkanHardwareCompute.version == 0);
+        }
 
         JSONObject bestDevice = null;
         int bestDeviceLevel = -1;
+        int bestComputeLevel = -1;
         int bestDeviceVersion = -1;
         for (JSONObject device : mVulkanDevices) {
             int level = determineHardwareLevel(device);
+            int compute = determineHardwareCompute(device);
             int version = determineHardwareVersion(device);
             if (DEBUG) {
                 Log.d(TAG, device.getJSONObject("properties").getString("deviceName") +
-                    ": level=" + level + " version=0x" + Integer.toHexString(version));
+                    ": level=" + level + " compute=" + compute +
+                    " version=0x" + Integer.toHexString(version));
             }
-            if (level >= bestDeviceLevel && version >= bestDeviceVersion) {
+            if (level >= bestDeviceLevel && compute >= bestComputeLevel &&
+                    version >= bestDeviceVersion) {
                 bestDevice = device;
                 bestDeviceLevel = level;
+                bestComputeLevel = compute;
                 bestDeviceVersion = version;
             }
         }
@@ -144,6 +164,16 @@
             " isn't close enough (same major and minor version, less or equal patch version)" +
             " to best physical device version 0x" + Integer.toHexString(bestDeviceVersion),
             isVersionCompatible(bestDeviceVersion, mVulkanHardwareVersion.version));
+        if (mVulkanHardwareCompute == null) {
+            assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE +
+                " not present, but required features are supported",
+                bestComputeLevel, -1);
+        } else {
+            assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE +
+                " version " + mVulkanHardwareCompute.version +
+                " doesn't match best physical device hardware compute " + bestComputeLevel,
+                bestComputeLevel, mVulkanHardwareCompute.version);
+        }
     }
 
     @Test
@@ -190,6 +220,23 @@
         return 1;
     }
 
+    private int determineHardwareCompute(JSONObject device) throws JSONException {
+        boolean have16bitStorage = false;
+        boolean haveVariablePointers = false;
+        JSONArray extensions = device.getJSONArray("extensions");
+        for (int i = 0; i < extensions.length(); i++) {
+            String name = extensions.getJSONObject(i).getString("extensionName");
+            if (name.equals("VK_KHR_16bit_storage"))
+                have16bitStorage = true;
+            else if (name.equals("VK_KHR_variable_pointers"))
+                haveVariablePointers = true;
+        }
+        if (!have16bitStorage || !haveVariablePointers) {
+            return -1;
+        }
+        return 0;
+    }
+
     private int determineHardwareVersion(JSONObject device) throws JSONException {
         return device.getJSONObject("properties").getInt("apiVersion");
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
index ee24eed..2392249 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
@@ -16,6 +16,7 @@
 
 package android.keystore.cts;
 
+import android.security.GateKeeper;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.test.MoreAsserts;
@@ -47,6 +48,7 @@
         MoreAsserts.assertEmpty(Arrays.asList(spec.getSignaturePaddings()));
         assertFalse(spec.isUserAuthenticationRequired());
         assertEquals(-1, spec.getUserAuthenticationValidityDurationSeconds());
+        assertEquals(GateKeeper.INVALID_SECURE_USER_ID, spec.getBoundToSpecificSecureUserId());
     }
 
     public void testSettersReflectedInGetters() {
@@ -70,6 +72,7 @@
                         KeyProperties.SIGNATURE_PADDING_RSA_PSS)
                 .setUserAuthenticationRequired(true)
                 .setUserAuthenticationValidityDurationSeconds(123456)
+                .setBoundToSpecificSecureUserId(654321)
                 .build();
 
         assertEquals(
@@ -89,6 +92,7 @@
                 KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, KeyProperties.SIGNATURE_PADDING_RSA_PSS);
         assertTrue(spec.isUserAuthenticationRequired());
         assertEquals(123456, spec.getUserAuthenticationValidityDurationSeconds());
+        assertEquals(654321, spec.getBoundToSpecificSecureUserId());
     }
 
     public void testSetKeyValidityEndDateAppliesToBothEndDates() {
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
index f16cadd..5993a95 100644
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
@@ -499,6 +499,7 @@
         result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired());
         result.setUserAuthenticationValidityDurationSeconds(
                 spec.getUserAuthenticationValidityDurationSeconds());
+        result.setBoundToSpecificSecureUserId(spec.getBoundToSpecificSecureUserId());
         return result;
     }
 
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index cae1611..9dc6943 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -422,7 +422,7 @@
     }
 
     public void testVolumeDndAffectedStream() throws Exception {
-        if (mUseFixedVolume || mHasVibrator) {
+        if (mUseFixedVolume || mHasVibrator || mIsTelevision) {
             return;
         }
         Utils.toggleNotificationPolicyAccess(
@@ -634,7 +634,7 @@
     }
 
     public void testMuteDndAffectedStreams() throws Exception {
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || mIsTelevision) {
             return;
         }
         int[] streams = { AudioManager.STREAM_RING };
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
index 7c81816..9de3bd2 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
@@ -45,9 +45,7 @@
 public class AudioTrackSurroundTest extends CtsAndroidTestCase {
     private static final String TAG = "AudioTrackSurroundTest";
 
-    // We typically find tolerance to be within 0.2 percent, but we allow one percent.
     private static final double MAX_RATE_TOLERANCE_FRACTION = 0.01;
-    private static final double MAX_INSTANTANEOUS_RATE_TOLERANCE_FRACTION = 0.15;
     private static final boolean LOG_TIMESTAMPS = false; // set true for debugging
 
     // Set this true to prefer the device that supports the particular encoding.
@@ -237,31 +235,54 @@
 
         void checkIndividualTimestamps(int sampleRate) {
             AudioTimestamp previous = null;
+            double sumDeltaSquared = 0.0;
+            int populationSize = 0;
+            double maxDeltaMillis = 0.0;
             // Make sure the timestamps are smooth and don't go retrograde.
             for (AudioTimestamp timestamp : mTimestamps) {
                 if (previous != null) {
-                    final double TOLERANCE_MILLIS = 2.0;
 
-                    assertTrue("framePosition should be monotonic",
+                    assertTrue("framePosition must be monotonic",
                             timestamp.framePosition >= previous.framePosition);
-                    assertTrue("nanoTime should be monotonic",
+                    assertTrue("nanoTime must be monotonic",
                             timestamp.nanoTime >= previous.nanoTime);
 
                     if (timestamp.framePosition > previous.framePosition) {
+                        // Measure timing jitter.
                         // Calculate predicted duration based on measured rate and compare
                         // it with actual duration.
+                        final double TOLERANCE_MILLIS = 2.0;
                         long elapsedFrames = timestamp.framePosition - previous.framePosition;
                         long elapsedNanos = timestamp.nanoTime - previous.nanoTime;
-                        double expectedNanos = elapsedFrames * (double) NANOS_PER_SECOND / sampleRate;
-                        assertEquals("elapsed time should match predicted duration"
-                                + ", sampleRate = " + sampleRate
-                                + ", framePosition = " + timestamp.framePosition,
-                                expectedNanos, (double) elapsedNanos,
-                                TOLERANCE_MILLIS * NANOS_PER_MILLISECOND);
+                        double measuredMillis = elapsedNanos / (double) NANOS_PER_MILLISECOND;
+                        double expectedMillis = elapsedFrames * (double) MILLIS_PER_SECOND
+                            / sampleRate;
+                        double deltaMillis = measuredMillis - expectedMillis;
+                        sumDeltaSquared += deltaMillis * deltaMillis;
+                        populationSize++;
+                        // We only issue a warning here because the CDD does not mandate a
+                        // specific tolerance.
+                        double absDeltaMillis = Math.abs(deltaMillis);
+                        if (absDeltaMillis > TOLERANCE_MILLIS) {
+                            Log.w(TAG, "measured time exceeds expected"
+                                + ", srate = " + sampleRate
+                                + ", frame = " + timestamp.framePosition
+                                + ", expected = " + expectedMillis
+                                + ", measured = " + measuredMillis + " (msec)"
+                                );
+                        }
+                        if (absDeltaMillis > maxDeltaMillis) {
+                            maxDeltaMillis = absDeltaMillis;
+                        }
                     }
                 }
                 previous = timestamp;
             }
+            Log.d(TAG, "max abs(delta) from expected duration = " + maxDeltaMillis + " msec");
+            if (populationSize > 0) {
+                double deviation = Math.sqrt(sumDeltaSquared / populationSize);
+                Log.d(TAG, "standard deviation from expected duration = " + deviation + " msec");
+            }
         }
 
         // Use collected timestamps to estimate a sample rate.
@@ -414,10 +435,11 @@
 
                 // Estimate the sample rate and compare it with expected.
                 double estimatedRate = mTimestampAnalyzer.estimateSampleRate();
+                Log.d(TAG, "measured sample rate = " + estimatedRate);
                 assertEquals(TEST_NAME + ": measured sample rate" + getPcmWarning(),
                         mSampleRate, estimatedRate, mSampleRate * MAX_RATE_TOLERANCE_FRACTION);
 
-                // Check for jitter of retrograde motion in each timestamp.
+                // Check for jitter or retrograde motion in each timestamp.
                 mTimestampAnalyzer.checkIndividualTimestamps(mSampleRate);
 
             } finally {
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 13484b2..3d853d4 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -373,6 +373,43 @@
                 aa.getContentType());
     }
 
+    // Test case 5: build AudioTrack with attributes and performance mode
+    public void testBuilderAttributesPerformanceMode() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testBuilderAttributesPerformanceMode";
+        final int testPerformanceModes[] = new int[] {
+            AudioTrack.PERFORMANCE_MODE_NONE,
+            AudioTrack.PERFORMANCE_MODE_LOW_LATENCY,
+            AudioTrack.PERFORMANCE_MODE_POWER_SAVING,
+        };
+        // construct various attributes with different preset performance modes.
+        final AudioAttributes testAttributes[] = new AudioAttributes[] {
+            new AudioAttributes.Builder().build(),
+            new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_LOW_LATENCY).build(),
+            new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_DEEP_BUFFER).build(),
+        };
+        for (int performanceMode : testPerformanceModes) {
+            for (AudioAttributes attributes : testAttributes) {
+                final AudioTrack track = new AudioTrack.Builder()
+                    .setPerformanceMode(performanceMode)
+                    .setAudioAttributes(attributes)
+                    .build();
+                // save results
+                final int actualPerformanceMode = track.getPerformanceMode();
+                // release track before the test exits
+                track.release();
+                final String result = "Attribute flags: " + attributes.getAllFlags()
+                        + " set performance mode: " + performanceMode
+                        + " actual performance mode: " + actualPerformanceMode;
+                Log.d(TEST_NAME, result);
+                assertTrue(TEST_NAME + ": " + result,
+                        actualPerformanceMode == performanceMode  // either successful
+                        || actualPerformanceMode == AudioTrack.PERFORMANCE_MODE_NONE // or none
+                        || performanceMode == AudioTrack.PERFORMANCE_MODE_NONE);
+            }
+        }
+    }
+
     // -----------------------------------------------------------------
     // Playback head position
     // ----------------------------------
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
index 9ec4956..e13bba6 100644
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
@@ -19,8 +19,10 @@
 import android.media.AudioManager;
 import android.media.Rating;
 import android.media.VolumeProvider;
+import android.media.MediaDescription;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
 import android.media.session.PlaybackState;
 import android.media.session.PlaybackState.CustomAction;
 import android.net.Uri;
@@ -51,6 +53,7 @@
         super.setUp();
         mSession = new MediaSession(getContext(), SESSION_TAG);
         mSession.setCallback(mCallback, mHandler);
+        mSession.setFlags(MediaSession.FLAG_HANDLES_QUEUE_COMMANDS);
         mController = mSession.getController();
     }
 
@@ -85,6 +88,44 @@
         }
     }
 
+    public void testAddRemoveQueueItems() throws Exception {
+        final String mediaId = "media_id";
+        final String mediaTitle = "media_title";
+        MediaDescription itemDescription = new MediaDescription.Builder()
+                .setMediaId(mediaId).setTitle(mediaTitle).build();
+
+        synchronized (mWaitLock) {
+            mCallback.reset();
+            mController.addQueueItem(itemDescription);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnAddQueueItemCalled);
+            assertEquals(-1, mCallback.mQueueIndex);
+            assertEquals(mediaId, mCallback.mQueueDescription.getMediaId());
+            assertEquals(mediaTitle, mCallback.mQueueDescription.getTitle());
+
+            mCallback.reset();
+            mController.addQueueItem(itemDescription, 0);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnAddQueueItemAtCalled);
+            assertEquals(0, mCallback.mQueueIndex);
+            assertEquals(mediaId, mCallback.mQueueDescription.getMediaId());
+            assertEquals(mediaTitle, mCallback.mQueueDescription.getTitle());
+
+            mCallback.reset();
+            mController.removeQueueItemAt(0);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnRemoveQueueItemAtCalled);
+            assertEquals(0, mCallback.mQueueIndex);
+
+            mCallback.reset();
+            mController.removeQueueItem(itemDescription);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnRemoveQueueItemCalled);
+            assertEquals(mediaId, mCallback.mQueueDescription.getMediaId());
+            assertEquals(mediaTitle, mCallback.mQueueDescription.getTitle());
+        }
+    }
+
     public void testVolumeControl() throws Exception {
         VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_ABSOLUTE, 11, 5) {
             @Override
@@ -295,6 +336,8 @@
     private class MediaSessionCallback extends MediaSession.Callback {
         private long mSeekPosition;
         private long mQueueItemId;
+        private int mQueueIndex;
+        private MediaDescription mQueueDescription;
         private Rating mRating;
         private String mMediaId;
         private String mQuery;
@@ -327,10 +370,16 @@
         private boolean mOnPrepareFromUriCalled;
         private boolean mOnSetRepeatModeCalled;
         private boolean mOnSetShuffleModeEnabledCalled;
+        private boolean mOnAddQueueItemCalled;
+        private boolean mOnAddQueueItemAtCalled;
+        private boolean mOnRemoveQueueItemCalled;
+        private boolean mOnRemoveQueueItemAtCalled;
 
         public void reset() {
             mSeekPosition = -1;
             mQueueItemId = -1;
+            mQueueIndex = -1;
+            mQueueDescription = null;
             mRating = null;
             mMediaId = null;
             mQuery = null;
@@ -363,6 +412,10 @@
             mOnPrepareFromUriCalled = false;
             mOnSetRepeatModeCalled = false;
             mOnSetShuffleModeEnabledCalled = false;
+            mOnAddQueueItemCalled = false;
+            mOnAddQueueItemAtCalled = false;
+            mOnRemoveQueueItemCalled = false;
+            mOnRemoveQueueItemAtCalled = false;
         }
 
         @Override
@@ -554,5 +607,42 @@
                 mWaitLock.notify();
             }
         }
+
+        @Override
+        public void onAddQueueItem(MediaDescription description) {
+            synchronized (mWaitLock) {
+                mOnAddQueueItemCalled = true;
+                mQueueDescription = description;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onAddQueueItem(MediaDescription description, int index) {
+            synchronized (mWaitLock) {
+                mOnAddQueueItemAtCalled = true;
+                mQueueIndex = index;
+                mQueueDescription = description;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onRemoveQueueItem(MediaDescription description) {
+            synchronized (mWaitLock) {
+                mOnRemoveQueueItemCalled = true;
+                mQueueDescription = description;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onRemoveQueueItemAt(int index) {
+            synchronized (mWaitLock) {
+                mOnRemoveQueueItemAtCalled = true;
+                mQueueIndex = index;
+                mWaitLock.notify();
+            }
+        }
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
index c460519..0bbbe95 100644
--- a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
@@ -60,28 +60,28 @@
         int source = R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz;
         String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideo", ".mp4")
                 .getAbsolutePath();
-        cloneAndVerify(source, outputFile, 2, 90);
+        cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
     }
 
     public void testDualVideoTrack() throws Exception {
         int source = R.raw.video_176x144_h264_408kbps_30fps_352x288_h264_122kbps_30fps;
         String outputFile = File.createTempFile("MediaMuxerTest_testDualVideo", ".mp4")
                 .getAbsolutePath();
-        cloneAndVerify(source, outputFile, 2, 90);
+        cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
     }
 
     public void testDualAudioTrack() throws Exception {
         int source = R.raw.audio_aac_mono_70kbs_44100hz_aac_mono_70kbs_44100hz;
         String outputFile = File.createTempFile("MediaMuxerTest_testDualAudio", ".mp4")
                 .getAbsolutePath();
-        cloneAndVerify(source, outputFile, 2, 90);
+        cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
     }
 
     public void testDualVideoAndAudioTrack() throws Exception {
         int source = R.raw.video_h264_30fps_video_h264_30fps_aac_44100hz_aac_44100hz;
         String outputFile = File.createTempFile("MediaMuxerTest_testDualVideoAudio", ".mp4")
                 .getAbsolutePath();
-        cloneAndVerify(source, outputFile, 4, 90);
+        cloneAndVerify(source, outputFile, 4, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
     }
 
     /**
@@ -92,7 +92,7 @@
                 R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro;
         String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
                 .getAbsolutePath();
-        cloneAndVerify(source, outputFile, 3, 90);
+        cloneAndVerify(source, outputFile, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
     }
 
     /**
@@ -102,7 +102,7 @@
         int source = R.raw.sinesweepm4a;
         String outputFile = File.createTempFile("MediaMuxerTest_testAudioOnly", ".mp4")
                 .getAbsolutePath();
-        cloneAndVerify(source, outputFile, 1, -1);
+        cloneAndVerify(source, outputFile, 1, -1, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
     }
 
     /**
@@ -112,7 +112,21 @@
         int source = R.raw.video_only_176x144_3gp_h263_25fps;
         String outputFile = File.createTempFile("MediaMuxerTest_videoOnly", ".mp4")
                 .getAbsolutePath();
-        cloneAndVerify(source, outputFile, 1, 180);
+        cloneAndVerify(source, outputFile, 1, 180, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+    }
+
+    public void testWebmOutput() throws Exception {
+        int source = R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz;
+        String outputFile = File.createTempFile("testWebmOutput", ".webm")
+                .getAbsolutePath();
+        cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
+    }
+
+    public void testThreegppOutput() throws Exception {
+        int source = R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz;
+        String outputFile = File.createTempFile("testThreegppOutput", ".3gp")
+                .getAbsolutePath();
+        cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
     }
 
     /**
@@ -245,7 +259,7 @@
      * Using the MediaMuxer to clone a media file.
      */
     private void cloneMediaUsingMuxer(int srcMedia, String dstMediaPath,
-            int expectedTrackCount, int degrees) throws IOException {
+            int expectedTrackCount, int degrees, int fmt) throws IOException {
         // Set up MediaExtractor to read from the source.
         AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMedia);
         MediaExtractor extractor = new MediaExtractor();
@@ -257,7 +271,7 @@
 
         // Set up MediaMuxer for the destination.
         MediaMuxer muxer;
-        muxer = new MediaMuxer(dstMediaPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+        muxer = new MediaMuxer(dstMediaPath, fmt);
 
         // Set up the tracks.
         HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
@@ -281,23 +295,26 @@
             muxer.setOrientationHint(degrees);
         }
 
-        // Test setLocation out of bound cases
-        try {
-            muxer.setLocation(BAD_LATITUDE, LONGITUDE);
-            fail("setLocation succeeded with bad argument: [" + BAD_LATITUDE + "," + LONGITUDE
+        if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
+            fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
+            // Test setLocation out of bound cases
+            try {
+                muxer.setLocation(BAD_LATITUDE, LONGITUDE);
+                fail("setLocation succeeded with bad argument: [" + BAD_LATITUDE + "," + LONGITUDE
                     + "]");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-        try {
-            muxer.setLocation(LATITUDE, BAD_LONGITUDE);
-            fail("setLocation succeeded with bad argument: [" + LATITUDE + "," + BAD_LONGITUDE
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+            try {
+                muxer.setLocation(LATITUDE, BAD_LONGITUDE);
+                fail("setLocation succeeded with bad argument: [" + LATITUDE + "," + BAD_LONGITUDE
                     + "]");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
 
-        muxer.setLocation(LATITUDE, LONGITUDE);
+            muxer.setLocation(LATITUDE, LONGITUDE);
+        }
 
         muxer.start();
         while (!sawEOS) {
@@ -341,11 +358,15 @@
      * sure they match.
      */
     private void cloneAndVerify(int srcMedia, String outputMediaFile,
-            int expectedTrackCount, int degrees) throws IOException {
+            int expectedTrackCount, int degrees, int fmt) throws IOException {
         try {
-            cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount, degrees);
-            verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
-            verifyLocationInFile(outputMediaFile);
+            cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount,
+                    degrees, fmt);
+            if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
+                    fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
+                verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
+                verifyLocationInFile(outputMediaFile);
+            }
             // Check the sample on 1s and 0.5s.
             verifySamplesMatch(srcMedia, outputMediaFile, 1000000);
             verifySamplesMatch(srcMedia, outputMediaFile, 500000);
diff --git a/tests/tests/nativehardware/Android.mk b/tests/tests/nativehardware/Android.mk
index 3abaccb..d31af82 100644
--- a/tests/tests/nativehardware/Android.mk
+++ b/tests/tests/nativehardware/Android.mk
@@ -23,21 +23,20 @@
 LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
 LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
 
-#LOCAL_C_INCLUDES := \
-#    $(call include-path-for, wilhelm) \
-#    $(call include-path-for, wilhelm-ut)
-
 LOCAL_SRC_FILES := \
-    src/AHardwareBufferTest.cpp
+    src/AHardwareBufferTest.cpp \
+    src/Gralloc1MapperTest.cpp
 
 LOCAL_SHARED_LIBRARIES := \
   libandroid \
   libandroid_runtime \
+  libui \
   libutils \
   liblog \
 
 LOCAL_STATIC_LIBRARIES := \
   libgtest \
+  libgtest_main \
 
 LOCAL_CTS_TEST_PACKAGE := android.nativehardware
 # Tag this module as a cts test artifact
diff --git a/tests/tests/nativehardware/src/AHardwareBufferTest.cpp b/tests/tests/nativehardware/src/AHardwareBufferTest.cpp
index f851d87..1f31e5e 100644
--- a/tests/tests/nativehardware/src/AHardwareBufferTest.cpp
+++ b/tests/tests/nativehardware/src/AHardwareBufferTest.cpp
@@ -61,12 +61,13 @@
     if (static_cast<uint32_t>(buffer->layerCount) != desc.layers)
         return BuildFailureMessage(desc.layers,
                 static_cast<uint32_t>(buffer->layerCount), "layers");
-    if (static_cast<uint32_t>(buffer->usage) !=
-            android_hardware_HardwareBuffer_convertToGrallocUsageBits(
-                    desc.usage0, desc.usage1))
-        return BuildFailureMessage(
-                android_hardware_HardwareBuffer_convertToGrallocUsageBits(
-                        desc.usage0, desc.usage1),
+    uint64_t producerUsage = 0;
+    uint64_t consumerUsage = 0;
+    android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+            desc.usage0, desc.usage1, &producerUsage, &consumerUsage);
+    uint64_t combinedUsage = producerUsage | consumerUsage;
+    if (static_cast<uint32_t>(buffer->usage) != combinedUsage)
+        return BuildFailureMessage(combinedUsage,
                 static_cast<uint32_t>(buffer->usage), "usages");
     if (android_hardware_HardwareBuffer_convertFromPixelFormat(
             buffer->getPixelFormat()) != desc.format)
@@ -298,8 +299,3 @@
 
     AHardwareBuffer_release(buffer);
 }
-
-int main(int argc, char **argv) {
-    testing::InitGoogleTest(&argc, argv);
-    return RUN_ALL_TESTS();
-}
diff --git a/tests/tests/nativehardware/src/Gralloc1MapperTest.cpp b/tests/tests/nativehardware/src/Gralloc1MapperTest.cpp
new file mode 100644
index 0000000..b7c9f0f
--- /dev/null
+++ b/tests/tests/nativehardware/src/Gralloc1MapperTest.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Gralloc1Mapper_test"
+//#define LOG_NDEBUG 0
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <ui/GraphicBuffer.h>
+#include <ui/GraphicBufferMapper.h>
+#include <utils/Errors.h>
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+class Gralloc1MapperTest : public ::testing::Test
+{
+public:
+    ~Gralloc1MapperTest() override = default;
+
+protected:
+    void SetUp() override {
+        buffer = new GraphicBuffer(4, 8, HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                GRALLOC1_PRODUCER_USAGE_CPU_WRITE_OFTEN,
+                GRALLOC1_CONSUMER_USAGE_CPU_READ_OFTEN, "Gralloc1MapperTest");
+        ASSERT_NE(nullptr, buffer.get());
+
+        handle = static_cast<buffer_handle_t>(buffer->handle);
+
+        mapper = &GraphicBufferMapper::get();
+    }
+
+    sp<GraphicBuffer> buffer;
+    buffer_handle_t handle;
+    GraphicBufferMapper* mapper;
+};
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getDimensions) {
+    uint32_t width = 0;
+    uint32_t height = 0;
+    status_t err = mapper->getDimensions(handle, &width, &height);
+    ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+    EXPECT_EQ(4U, width);
+    EXPECT_EQ(8U, height);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getFormat) {
+    int32_t value = 0;
+    status_t err = mapper->getFormat(handle, &value);
+    ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+    EXPECT_EQ(HAL_PIXEL_FORMAT_RGBA_8888, value);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getLayerCount) {
+    uint32_t value = 0;
+    status_t err = mapper->getLayerCount(handle, &value);
+    if (err != GRALLOC1_ERROR_UNSUPPORTED) {
+        EXPECT_EQ(1U, value);
+    }
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getProducerUsage) {
+    uint64_t value = 0;
+    status_t err = mapper->getProducerUsage(handle, &value);
+    ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+    EXPECT_EQ(GRALLOC1_PRODUCER_USAGE_CPU_WRITE_OFTEN, value);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getConsumerUsage) {
+    uint64_t value = 0;
+    status_t err = mapper->getConsumerUsage(handle, &value);
+    ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+    EXPECT_EQ(GRALLOC1_CONSUMER_USAGE_CPU_READ_OFTEN, value);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getBackingStore) {
+    uint64_t value = 0;
+    status_t err = mapper->getBackingStore(handle, &value);
+    ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getStride) {
+    uint32_t value = 0;
+    status_t err = mapper->getStride(handle, &value);
+    ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+    // The stride should be at least the width of the buffer.
+    EXPECT_LE(4U, value);
+}
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
index f6cdadb..894c2b8 100644
--- a/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
+++ b/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
@@ -20,167 +20,167 @@
 #include <gtest/gtest.h>
 #include <utils/Log.h>
 
-#include <oboe/OboeAudio.h>
-#include <oboe/OboeDefinitions.h>
+#include <aaudio/AAudio.h>
+#include <aaudio/AAudioDefinitions.h>
 
-// Note that this "Oboe" audio API is in the process of being renamed "AAudio".
+// Note that this "AAudio" audio API is in the process of being renamed "AAudio".
 // You may see both names until the conversion is complete.
-// TODO Remove this comment when Oboe has become AAudio.
+// TODO Remove this comment when AAudio has become AAudio.
 
-#define DEFAULT_STATE_TIMEOUT  (500 * OBOE_NANOS_PER_MILLISECOND)
+#define DEFAULT_STATE_TIMEOUT  (500 * AAUDIO_NANOS_PER_MILLISECOND)
 
-// Test OboeStreamBuilder
-TEST(test_aaudio, oboe_stream_builder) {
-    const oboe_sample_rate_t requestedSampleRate1 = 48000;
-    const oboe_sample_rate_t requestedSampleRate2 = 44100;
+// Test AAudioStreamBuilder
+TEST(test_aaudio, aaudio_stream_builder) {
+    const aaudio_sample_rate_t requestedSampleRate1 = 48000;
+    const aaudio_sample_rate_t requestedSampleRate2 = 44100;
     const int32_t requestedSamplesPerFrame = 2;
-    const oboe_audio_format_t requestedDataFormat = OBOE_AUDIO_FORMAT_PCM16;
+    const aaudio_audio_format_t requestedDataFormat = AAUDIO_FORMAT_PCM16;
 
-    oboe_sample_rate_t sampleRate = -1;
+    aaudio_sample_rate_t sampleRate = -1;
     int32_t samplesPerFrame = -1;
-    oboe_audio_format_t actualDataFormat;
-    OboeStreamBuilder oboeBuilder1;
-    OboeStreamBuilder oboeBuilder2;
+    aaudio_audio_format_t actualDataFormat;
+    AAudioStreamBuilder aaudioBuilder1;
+    AAudioStreamBuilder aaudioBuilder2;
 
-    // Use an OboeStreamBuilder to define the stream.
-    oboe_result_t result = Oboe_createStreamBuilder(&oboeBuilder1);
-    ASSERT_EQ(OBOE_OK, result);
+    // Use an AAudioStreamBuilder to define the stream.
+    aaudio_result_t result = AAudio_createStreamBuilder(&aaudioBuilder1);
+    ASSERT_EQ(AAUDIO_OK, result);
 
     // Request stream properties.
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSampleRate(oboeBuilder1, requestedSampleRate1));
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSamplesPerFrame(oboeBuilder1,
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSampleRate(aaudioBuilder1, requestedSampleRate1));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSamplesPerFrame(aaudioBuilder1,
             requestedSamplesPerFrame));
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setFormat(oboeBuilder1, requestedDataFormat));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setFormat(aaudioBuilder1, requestedDataFormat));
 
     // Check to make sure builder saved the properties.
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder1, &sampleRate));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder1, &sampleRate));
     EXPECT_EQ(requestedSampleRate1, sampleRate);
 
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSamplesPerFrame(oboeBuilder1, &samplesPerFrame));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSamplesPerFrame(aaudioBuilder1, &samplesPerFrame));
     EXPECT_EQ(requestedSamplesPerFrame, samplesPerFrame);
 
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getFormat(oboeBuilder1, &actualDataFormat));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getFormat(aaudioBuilder1, &actualDataFormat));
     EXPECT_EQ(requestedDataFormat, actualDataFormat);
 
-    result = OboeStreamBuilder_getSampleRate(0x0BADCAFE, &sampleRate); // ridiculous token
-    EXPECT_EQ(OBOE_ERROR_INVALID_HANDLE, result);
+    result = AAudioStreamBuilder_getSampleRate(0x0BADCAFE, &sampleRate); // ridiculous token
+    EXPECT_EQ(AAUDIO_ERROR_INVALID_HANDLE, result);
 
     // Create a second builder and make sure they do not collide.
-    ASSERT_EQ(OBOE_OK, Oboe_createStreamBuilder(&oboeBuilder2));
-    ASSERT_NE(oboeBuilder1, oboeBuilder2);
+    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder2));
+    ASSERT_NE(aaudioBuilder1, aaudioBuilder2);
 
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSampleRate(oboeBuilder2, requestedSampleRate2));
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder1, &sampleRate));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSampleRate(aaudioBuilder2, requestedSampleRate2));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder1, &sampleRate));
     EXPECT_EQ(requestedSampleRate1, sampleRate);
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder2, &sampleRate));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder2, &sampleRate));
     EXPECT_EQ(requestedSampleRate2, sampleRate);
 
     // Delete the builder.
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder1));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder1));
 
     // Now it should no longer be valid.
     // Note that test assumes we are using the HandleTracker. If we use plain pointers
     // then it will be difficult to detect this kind of error.
-    result = OboeStreamBuilder_getSampleRate(oboeBuilder1, &sampleRate); // stale token
-    EXPECT_EQ(OBOE_ERROR_INVALID_HANDLE, result);
+    result = AAudioStreamBuilder_getSampleRate(aaudioBuilder1, &sampleRate); // stale token
+    EXPECT_EQ(AAUDIO_ERROR_INVALID_HANDLE, result);
 
     // Second builder should still be valid.
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder2, &sampleRate));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder2, &sampleRate));
     EXPECT_EQ(requestedSampleRate2, sampleRate);
 
     // Delete the second builder.
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder2));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder2));
 
     // Now it should no longer be valid. Assumes HandlerTracker used.
-    EXPECT_EQ(OBOE_ERROR_INVALID_HANDLE, OboeStreamBuilder_getSampleRate(oboeBuilder2,
+    EXPECT_EQ(AAUDIO_ERROR_INVALID_HANDLE, AAudioStreamBuilder_getSampleRate(aaudioBuilder2,
             &sampleRate));
 }
 
 // Test creating a default stream with everything unspecified.
-TEST(test_aaudio, oboe_stream_unspecified) {
-    OboeStreamBuilder oboeBuilder;
-    OboeStream oboeStream;
-    oboe_result_t result = OBOE_OK;
+TEST(test_aaudio, aaudio_stream_unspecified) {
+    AAudioStreamBuilder aaudioBuilder;
+    AAudioStream aaudioStream;
+    aaudio_result_t result = AAUDIO_OK;
 
-    // Use an OboeStreamBuilder to define the stream.
-    result = Oboe_createStreamBuilder(&oboeBuilder);
-    ASSERT_EQ(OBOE_OK, result);
+    // Use an AAudioStreamBuilder to define the stream.
+    result = AAudio_createStreamBuilder(&aaudioBuilder);
+    ASSERT_EQ(AAUDIO_OK, result);
 
-    // Create an OboeStream using the Builder.
-    ASSERT_EQ(OBOE_OK, OboeStreamBuilder_openStream(oboeBuilder, &oboeStream));
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
 
     // Cleanup
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder));
-    EXPECT_EQ(OBOE_OK, OboeStream_close(oboeStream));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
     // Can only close once. Second time should cause an error.
-    EXPECT_NE(OBOE_OK, OboeStream_close(oboeStream));
+    EXPECT_NE(AAUDIO_OK, AAudioStream_close(aaudioStream));
 }
 
-// Test Writing to an OboeStream
-void runtest_oboe_stream(oboe_sharing_mode_t requestedSharingMode) {
-    const oboe_sample_rate_t requestedSampleRate = 48000;
-    const oboe_sample_rate_t requestedSamplesPerFrame = 2;
-    const oboe_audio_format_t requestedDataFormat = OBOE_AUDIO_FORMAT_PCM16;
+// Test Writing to an AAudioStream
+void runtest_aaudio_stream(aaudio_sharing_mode_t requestedSharingMode) {
+    const aaudio_sample_rate_t requestedSampleRate = 48000;
+    const aaudio_sample_rate_t requestedSamplesPerFrame = 2;
+    const aaudio_audio_format_t requestedDataFormat = AAUDIO_FORMAT_PCM16;
 
-    oboe_sample_rate_t actualSampleRate = -1;
+    aaudio_sample_rate_t actualSampleRate = -1;
     int32_t actualSamplesPerFrame = -1;
-    oboe_audio_format_t actualDataFormat = OBOE_AUDIO_FORMAT_INVALID;
-    oboe_sharing_mode_t actualSharingMode;
-    oboe_size_frames_t framesPerBurst = -1;
+    aaudio_audio_format_t actualDataFormat = AAUDIO_FORMAT_INVALID;
+    aaudio_sharing_mode_t actualSharingMode;
+    aaudio_size_frames_t framesPerBurst = -1;
     int writeLoops = 0;
 
-    oboe_size_frames_t framesWritten = 0;
-    oboe_position_frames_t framesTotal = 0;
-    oboe_position_frames_t oboeFramesRead = 0;
-    oboe_position_frames_t oboeFramesRead1 = 0;
-    oboe_position_frames_t oboeFramesRead2 = 0;
-    oboe_position_frames_t oboeFramesWritten = 0;
+    aaudio_size_frames_t framesWritten = 0;
+    aaudio_position_frames_t framesTotal = 0;
+    aaudio_position_frames_t aaudioFramesRead = 0;
+    aaudio_position_frames_t aaudioFramesRead1 = 0;
+    aaudio_position_frames_t aaudioFramesRead2 = 0;
+    aaudio_position_frames_t aaudioFramesWritten = 0;
 
-    oboe_nanoseconds_t timeoutNanos;
+    aaudio_nanoseconds_t timeoutNanos;
 
-    oboe_stream_state_t state = OBOE_STREAM_STATE_UNINITIALIZED;
-    OboeStreamBuilder oboeBuilder;
-    OboeStream oboeStream;
+    aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
+    AAudioStreamBuilder aaudioBuilder;
+    AAudioStream aaudioStream;
 
-    oboe_result_t result = OBOE_OK;
+    aaudio_result_t result = AAUDIO_OK;
 
-    // Use an OboeStreamBuilder to define the stream.
-    result = Oboe_createStreamBuilder(&oboeBuilder);
-    ASSERT_EQ(OBOE_OK, result);
+    // Use an AAudioStreamBuilder to define the stream.
+    result = AAudio_createStreamBuilder(&aaudioBuilder);
+    ASSERT_EQ(AAUDIO_OK, result);
 
     // Request stream properties.
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSampleRate(oboeBuilder, requestedSampleRate));
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSamplesPerFrame(oboeBuilder, requestedSamplesPerFrame));
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setFormat(oboeBuilder, requestedDataFormat));
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSharingMode(oboeBuilder, requestedSharingMode));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSampleRate(aaudioBuilder, requestedSampleRate));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSamplesPerFrame(aaudioBuilder, requestedSamplesPerFrame));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setFormat(aaudioBuilder, requestedDataFormat));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSharingMode(aaudioBuilder, requestedSharingMode));
 
-    // Create an OboeStream using the Builder.
-    ASSERT_EQ(OBOE_OK, OboeStreamBuilder_openStream(oboeBuilder, &oboeStream));
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder));
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
 
-    EXPECT_EQ(OBOE_OK, OboeStream_getState(oboeStream, &state));
-    EXPECT_EQ(OBOE_STREAM_STATE_OPEN, state);
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_getState(aaudioStream, &state));
+    EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
 
     // Check to see what kind of stream we actually got.
-    EXPECT_EQ(OBOE_OK, OboeStream_getSampleRate(oboeStream, &actualSampleRate));
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_getSampleRate(aaudioStream, &actualSampleRate));
     ASSERT_TRUE(actualSampleRate >= 44100 && actualSampleRate <= 96000);  // TODO what is range?
 
-    EXPECT_EQ(OBOE_OK, OboeStream_getSamplesPerFrame(oboeStream, &actualSamplesPerFrame));
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_getSamplesPerFrame(aaudioStream, &actualSamplesPerFrame));
     ASSERT_TRUE(actualSamplesPerFrame >= 1 && actualSamplesPerFrame <= 16); // TODO what is max?
 
-    EXPECT_EQ(OBOE_OK, OboeStream_getSharingMode(oboeStream, &actualSharingMode));
-    ASSERT_TRUE(actualSharingMode == OBOE_SHARING_MODE_EXCLUSIVE
-                || actualSharingMode == OBOE_SHARING_MODE_LEGACY);
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_getSharingMode(aaudioStream, &actualSharingMode));
+    ASSERT_TRUE(actualSharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE
+                || actualSharingMode == AAUDIO_SHARING_MODE_LEGACY);
 
-    EXPECT_EQ(OBOE_OK, OboeStream_getFormat(oboeStream, &actualDataFormat));
-    EXPECT_NE(OBOE_AUDIO_FORMAT_INVALID, actualDataFormat);
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_getFormat(aaudioStream, &actualDataFormat));
+    EXPECT_NE(AAUDIO_FORMAT_INVALID, actualDataFormat);
 
-    EXPECT_EQ(OBOE_OK, OboeStream_getFramesPerBurst(oboeStream, &framesPerBurst));
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesPerBurst(aaudioStream, &framesPerBurst));
     ASSERT_TRUE(framesPerBurst >= 16 && framesPerBurst <= 1024); // TODO what is min/max?
 
     // Allocate a buffer for the audio data.
     // TODO handle possibility of other data formats
-    ASSERT_TRUE(actualDataFormat == OBOE_AUDIO_FORMAT_PCM16);
+    ASSERT_TRUE(actualDataFormat == AAUDIO_FORMAT_PCM16);
     size_t dataSizeSamples = framesPerBurst * actualSamplesPerFrame;
     int16_t *data = (int16_t *) calloc(dataSizeSamples, sizeof(int16_t));
     ASSERT_TRUE(nullptr != data);
@@ -188,7 +188,7 @@
     // Prime the buffer.
     timeoutNanos = 0;
     do {
-        framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+        framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
         // There should be some room for priming the buffer.
         framesTotal += framesWritten;
         ASSERT_GE(framesWritten, 0);
@@ -200,170 +200,170 @@
     // Write some data and measure the rate to see if the timing is OK.
     for (int numLoops = 0; numLoops < 2; numLoops++) {
         // Start and wait for server to respond.
-        ASSERT_EQ(OBOE_OK, OboeStream_requestStart(oboeStream));
-        ASSERT_EQ(OBOE_OK, OboeStream_waitForStateChange(oboeStream,
-                                                         OBOE_STREAM_STATE_STARTING,
+        ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
+        ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+                                                         AAUDIO_STREAM_STATE_STARTING,
                                                          &state,
                                                          DEFAULT_STATE_TIMEOUT));
-        EXPECT_EQ(OBOE_STREAM_STATE_STARTED, state);
+        EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
 
         // Write some data while we are running. Read counter should be advancing.
         writeLoops = 1 * actualSampleRate / framesPerBurst; // 1 second
         ASSERT_LT(2, writeLoops); // detect absurdly high framesPerBurst
-        timeoutNanos = 10 * OBOE_NANOS_PER_SECOND * framesPerBurst / actualSampleRate; // bursts
+        timeoutNanos = 10 * AAUDIO_NANOS_PER_SECOND * framesPerBurst / actualSampleRate; // bursts
         framesWritten = 1;
-        ASSERT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
-        oboeFramesRead1 = oboeFramesRead;
-        oboe_nanoseconds_t beginTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
+        ASSERT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
+        aaudioFramesRead1 = aaudioFramesRead;
+        aaudio_nanoseconds_t beginTime = AAudio_getNanoseconds(AAUDIO_CLOCK_MONOTONIC);
         do {
-            framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+            framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
             ASSERT_GE(framesWritten, 0);
             ASSERT_LE(framesWritten, framesPerBurst);
 
             framesTotal += framesWritten;
-            EXPECT_EQ(OBOE_OK, OboeStream_getFramesWritten(oboeStream, &oboeFramesWritten));
-            EXPECT_EQ(framesTotal, oboeFramesWritten);
+            EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesWritten(aaudioStream, &aaudioFramesWritten));
+            EXPECT_EQ(framesTotal, aaudioFramesWritten);
 
             // Try to get a more accurate measure of the sample rate.
             if (beginTime == 0) {
-                EXPECT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
-                if (oboeFramesRead > oboeFramesRead1) { // is read pointer advancing
-                    beginTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
-                    oboeFramesRead1 = oboeFramesRead;
+                EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
+                if (aaudioFramesRead > aaudioFramesRead1) { // is read pointer advancing
+                    beginTime = AAudio_getNanoseconds(AAUDIO_CLOCK_MONOTONIC);
+                    aaudioFramesRead1 = aaudioFramesRead;
                 }
             }
         } while (framesWritten > 0 && writeLoops-- > 0);
 
-        EXPECT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead2));
-        oboe_nanoseconds_t endTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
-        ASSERT_GT(oboeFramesRead2, 0);
-        ASSERT_GT(oboeFramesRead2, oboeFramesRead1);
-        ASSERT_LE(oboeFramesRead2, oboeFramesWritten);
+        EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead2));
+        aaudio_nanoseconds_t endTime = AAudio_getNanoseconds(AAUDIO_CLOCK_MONOTONIC);
+        ASSERT_GT(aaudioFramesRead2, 0);
+        ASSERT_GT(aaudioFramesRead2, aaudioFramesRead1);
+        ASSERT_LE(aaudioFramesRead2, aaudioFramesWritten);
 
         // TODO why is legacy so inaccurate?
         const double rateTolerance = 200.0; // arbitrary tolerance for sample rate
-        if (requestedSharingMode != OBOE_SHARING_MODE_LEGACY) {
+        if (requestedSharingMode != AAUDIO_SHARING_MODE_LEGACY) {
             // Calculate approximate sample rate and compare with stream rate.
-            double seconds = (endTime - beginTime) / (double) OBOE_NANOS_PER_SECOND;
-            double measuredRate = (oboeFramesRead2 - oboeFramesRead1) / seconds;
+            double seconds = (endTime - beginTime) / (double) AAUDIO_NANOS_PER_SECOND;
+            double measuredRate = (aaudioFramesRead2 - aaudioFramesRead1) / seconds;
             ASSERT_NEAR(actualSampleRate, measuredRate, rateTolerance);
         }
 
         // Request async pause and wait for server to say that it has completed the pause.
-        ASSERT_EQ(OBOE_OK, OboeStream_requestPause(oboeStream));
-        EXPECT_EQ(OBOE_OK, OboeStream_waitForStateChange(oboeStream,
-                                                OBOE_STREAM_STATE_PAUSING,
+        ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+        EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+                                                AAUDIO_STREAM_STATE_PAUSING,
                                                 &state,
                                                 DEFAULT_STATE_TIMEOUT));
-        EXPECT_EQ(OBOE_STREAM_STATE_PAUSED, state);
+        EXPECT_EQ(AAUDIO_STREAM_STATE_PAUSED, state);
     }
 
     // Make sure the read counter is not advancing when we are paused.
-    ASSERT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
-    ASSERT_GE(oboeFramesRead, oboeFramesRead2); // monotonic increase
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
+    ASSERT_GE(aaudioFramesRead, aaudioFramesRead2); // monotonic increase
 
     // Use this to sleep by waiting for something that won't happen.
-    OboeStream_waitForStateChange(oboeStream, OBOE_STREAM_STATE_PAUSED, &state, timeoutNanos);
-    ASSERT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead2));
-    EXPECT_EQ(oboeFramesRead, oboeFramesRead2);
+    AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_PAUSED, &state, timeoutNanos);
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead2));
+    EXPECT_EQ(aaudioFramesRead, aaudioFramesRead2);
 
     // ------------------- TEST FLUSH -----------------
     // Prime the buffer.
     timeoutNanos = 0;
     writeLoops = 100;
     do {
-        framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+        framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
         framesTotal += framesWritten;
     } while (framesWritten > 0 && writeLoops-- > 0);
     EXPECT_EQ(0, framesWritten);
 
     // Flush and wait for server to respond.
-    ASSERT_EQ(OBOE_OK, OboeStream_requestFlush(oboeStream));
-    EXPECT_EQ(OBOE_OK, OboeStream_waitForStateChange(oboeStream,
-                                                     OBOE_STREAM_STATE_FLUSHING,
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestFlush(aaudioStream));
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+                                                     AAUDIO_STREAM_STATE_FLUSHING,
                                                      &state,
                                                      DEFAULT_STATE_TIMEOUT));
-    EXPECT_EQ(OBOE_STREAM_STATE_FLUSHED, state);
+    EXPECT_EQ(AAUDIO_STREAM_STATE_FLUSHED, state);
 
     // After a flush, the read counter should be caught up with the write counter.
-    EXPECT_EQ(OBOE_OK, OboeStream_getFramesWritten(oboeStream, &oboeFramesWritten));
-    EXPECT_EQ(framesTotal, oboeFramesWritten);
-    EXPECT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
-    EXPECT_EQ(oboeFramesRead, oboeFramesWritten);
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesWritten(aaudioStream, &aaudioFramesWritten));
+    EXPECT_EQ(framesTotal, aaudioFramesWritten);
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
+    EXPECT_EQ(aaudioFramesRead, aaudioFramesWritten);
 
     // The buffer should be empty after a flush so we should be able to write.
-    framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+    framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
     // There should be some room for priming the buffer.
     ASSERT_TRUE(framesWritten > 0 && framesWritten <= framesPerBurst);
 
-    EXPECT_EQ(OBOE_OK, OboeStream_close(oboeStream));
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
     free(data);
 }
 
-// Test Writing to an OboeStream using LEGACY sharing mode.
-TEST(test_aaudio, oboe_stream_legacy) {
-    runtest_oboe_stream(OBOE_SHARING_MODE_LEGACY);
+// Test Writing to an AAudioStream using LEGACY sharing mode.
+TEST(test_aaudio, aaudio_stream_legacy) {
+    runtest_aaudio_stream(AAUDIO_SHARING_MODE_LEGACY);
 }
 
 /* TODO Enable exclusive mode test.
-// Test Writing to an OboeStream using EXCLUSIVE sharing mode.
-TEST(test_aaudio, oboe_stream_exclusive) {
-    runtest_oboe_stream(OBOE_SHARING_MODE_EXCLUSIVE);
+// Test Writing to an AAudioStream using EXCLUSIVE sharing mode.
+TEST(test_aaudio, aaudio_stream_exclusive) {
+    runtest_aaudio_stream(AAUDIO_SHARING_MODE_EXCLUSIVE);
 }
 */
 
-#define OBOE_THREAD_ANSWER          1826375
-#define OBOE_THREAD_DURATION_MSEC       500
+#define AAUDIO_THREAD_ANSWER          1826375
+#define AAUDIO_THREAD_DURATION_MSEC       500
 
-static void *TestOboeStreamThreadProc(void *arg) {
-    OboeStream oboeStream = (OboeStream) reinterpret_cast<size_t>(arg);
-    oboe_stream_state_t state;
+static void *TestAAudioStreamThreadProc(void *arg) {
+    AAudioStream aaudioStream = (AAudioStream) reinterpret_cast<size_t>(arg);
+    aaudio_stream_state_t state;
 
     // Use this to sleep by waiting for something that won't happen.
-    EXPECT_EQ(OBOE_OK, OboeStream_getState(oboeStream, &state));
-    OboeStream_waitForStateChange(oboeStream, OBOE_STREAM_STATE_PAUSED, &state,
-            OBOE_THREAD_DURATION_MSEC * OBOE_NANOS_PER_MILLISECOND);
-    return reinterpret_cast<void *>(OBOE_THREAD_ANSWER);
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_getState(aaudioStream, &state));
+    AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_PAUSED, &state,
+            AAUDIO_THREAD_DURATION_MSEC * AAUDIO_NANOS_PER_MILLISECOND);
+    return reinterpret_cast<void *>(AAUDIO_THREAD_ANSWER);
 }
 
 // Test creating a stream related thread.
-TEST(test_aaudio, oboe_stream_thread_basic) {
-    OboeStreamBuilder oboeBuilder;
-    OboeStream oboeStream;
-    oboe_result_t result = OBOE_OK;
+TEST(test_aaudio, aaudio_stream_thread_basic) {
+    AAudioStreamBuilder aaudioBuilder;
+    AAudioStream aaudioStream;
+    aaudio_result_t result = AAUDIO_OK;
     void *threadResult;
 
-    // Use an OboeStreamBuilder to define the stream.
-    result = Oboe_createStreamBuilder(&oboeBuilder);
-    ASSERT_EQ(OBOE_OK, result);
+    // Use an AAudioStreamBuilder to define the stream.
+    result = AAudio_createStreamBuilder(&aaudioBuilder);
+    ASSERT_EQ(AAUDIO_OK, result);
 
-    // Create an OboeStream using the Builder.
-    ASSERT_EQ(OBOE_OK, OboeStreamBuilder_openStream(oboeBuilder, &oboeStream));
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
 
     // Start a thread.
-    ASSERT_EQ(OBOE_OK, OboeStream_createThread(oboeStream,
-            10 * OBOE_NANOS_PER_MILLISECOND,
-            TestOboeStreamThreadProc,
-            reinterpret_cast<void *>(oboeStream)));
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_createThread(aaudioStream,
+            10 * AAUDIO_NANOS_PER_MILLISECOND,
+            TestAAudioStreamThreadProc,
+            reinterpret_cast<void *>(aaudioStream)));
     // Thread already started.
-    ASSERT_NE(OBOE_OK, OboeStream_createThread(oboeStream,   // should fail!
-            10 * OBOE_NANOS_PER_MILLISECOND,
-            TestOboeStreamThreadProc,
-            reinterpret_cast<void *>(oboeStream)));
+    ASSERT_NE(AAUDIO_OK, AAudioStream_createThread(aaudioStream,   // should fail!
+            10 * AAUDIO_NANOS_PER_MILLISECOND,
+            TestAAudioStreamThreadProc,
+            reinterpret_cast<void *>(aaudioStream)));
 
     // Wait for the thread to finish.
-    ASSERT_EQ(OBOE_OK, OboeStream_joinThread(oboeStream,
-            &threadResult, 2 * OBOE_THREAD_DURATION_MSEC * OBOE_NANOS_PER_MILLISECOND));
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_joinThread(aaudioStream,
+            &threadResult, 2 * AAUDIO_THREAD_DURATION_MSEC * AAUDIO_NANOS_PER_MILLISECOND));
     // The thread returns a special answer.
-    ASSERT_EQ(OBOE_THREAD_ANSWER, (int)reinterpret_cast<size_t>(threadResult));
+    ASSERT_EQ(AAUDIO_THREAD_ANSWER, (int)reinterpret_cast<size_t>(threadResult));
 
     // Thread should already be joined.
-    ASSERT_NE(OBOE_OK, OboeStream_joinThread(oboeStream,  // should fail!
-            &threadResult, 2 * OBOE_THREAD_DURATION_MSEC * OBOE_NANOS_PER_MILLISECOND));
+    ASSERT_NE(AAUDIO_OK, AAudioStream_joinThread(aaudioStream,  // should fail!
+            &threadResult, 2 * AAUDIO_THREAD_DURATION_MSEC * AAUDIO_NANOS_PER_MILLISECOND));
 
     // Cleanup
-    EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder));
-    EXPECT_EQ(OBOE_OK, OboeStream_close(oboeStream));
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
 }
 
 int main(int argc, char **argv) {
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index b8478d2..185ebfa 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -387,6 +387,10 @@
      * Tests reporting of connectivity changed.
      */
     public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+            Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
+            return;
+        }
         ConnectivityReceiver.prepare();
 
         toggleWifi();
@@ -400,6 +404,10 @@
     }
 
     public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+            Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
+            return;
+        }
         ConnectivityReceiver.prepare();
         ConnectivityReceiver receiver = new ConnectivityReceiver();
         IntentFilter filter = new IntentFilter();
@@ -416,6 +424,10 @@
 
     public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
             throws InterruptedException {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+            Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
+            return;
+        }
         Intent startIntent = new Intent();
         startIntent.setComponent(new ComponentName("android.net.cts.appForApi23",
                 "android.net.cts.appForApi23.ConnectivityListeningActivity"));
diff --git a/tests/tests/os/src/android/os/cts/SecurityPatchTest.java b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
index 68609e1..4531aa68 100644
--- a/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
+++ b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
@@ -32,7 +32,7 @@
     private static final String SECURITY_PATCH_DATE_ERROR =
             "ro.build.version.security_patch should be \"%d-%02d\" or later. Found \"%s\"";
     private static final int SECURITY_PATCH_YEAR = 2016;
-    private static final int SECURITY_PATCH_MONTH = 06;
+    private static final int SECURITY_PATCH_MONTH = 12;
 
     private boolean mSkipTests = false;
 
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index df82225..845cf30 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -785,6 +785,92 @@
         assertTrue("/data is not mounted NODEV", (vfs.f_flag & OsConstants.ST_NODEV) != 0);
     }
 
+    public void testAllBlockDevicesAreSecure() throws Exception {
+        Set<File> insecure = getAllInsecureDevicesInDirAndSubdir(new File("/dev"), FileUtils.S_IFBLK);
+        assertTrue("Found insecure block devices: " + insecure.toString(),
+                insecure.isEmpty());
+    }
+
+    private static final Set<File> CHAR_DEV_EXCEPTIONS = new HashSet<File>(
+            Arrays.asList(
+                // All exceptions should be alphabetical and associated with a bug number.
+                new File("/dev/adsprpc-smd"), // b/11710243
+                new File("/dev/alarm"),      // b/9035217
+                new File("/dev/ashmem"),
+                new File("/dev/binder"),
+                new File("/dev/card0"),       // b/13159510
+                new File("/dev/renderD128"),
+                new File("/dev/renderD129"),  // b/23798677
+                new File("/dev/dri/card0"),   // b/13159510
+                new File("/dev/dri/renderD128"),
+                new File("/dev/dri/renderD129"), // b/23798677
+                new File("/dev/felica"),     // b/11142586
+                new File("/dev/felica_ant"), // b/11142586
+                new File("/dev/felica_cen"), // b/11142586
+                new File("/dev/felica_pon"), // b/11142586
+                new File("/dev/felica_rfs"), // b/11142586
+                new File("/dev/felica_rws"), // b/11142586
+                new File("/dev/felica_uicc"), // b/11142586
+                new File("/dev/full"),
+                new File("/dev/galcore"),
+                new File("/dev/genlock"),    // b/9035217
+                new File("/dev/graphics/galcore"),
+                new File("/dev/ion"),
+                new File("/dev/kgsl-2d0"),   // b/11271533
+                new File("/dev/kgsl-2d1"),   // b/11271533
+                new File("/dev/kgsl-3d0"),   // b/9035217
+                new File("/dev/log/events"), // b/9035217
+                new File("/dev/log/main"),   // b/9035217
+                new File("/dev/log/radio"),  // b/9035217
+                new File("/dev/log/system"), // b/9035217
+                new File("/dev/mali0"),       // b/9106968
+                new File("/dev/mali"),        // b/11142586
+                new File("/dev/mm_interlock"), // b/12955573
+                new File("/dev/mm_isp"),      // b/12955573
+                new File("/dev/mm_v3d"),      // b/12955573
+                new File("/dev/msm_rotator"), // b/9035217
+                new File("/dev/null"),
+                new File("/dev/nvhost-as-gpu"),
+                new File("/dev/nvhost-ctrl"), // b/9088251
+                new File("/dev/nvhost-ctrl-gpu"),
+                new File("/dev/nvhost-dbg-gpu"),
+                new File("/dev/nvhost-gpu"),
+                new File("/dev/nvhost-gr2d"), // b/9088251
+                new File("/dev/nvhost-gr3d"), // b/9088251
+                new File("/dev/nvhost-tsec"),
+                new File("/dev/nvhost-prof-gpu"),
+                new File("/dev/nvhost-vic"),
+                new File("/dev/nvmap"),       // b/9088251
+                new File("/dev/pmsg0"),       // b/31857082
+                new File("/dev/ptmx"),        // b/9088251
+                new File("/dev/pvrsrvkm"),    // b/9108170
+                new File("/dev/pvr_sync"),
+                new File("/dev/quadd"),
+                new File("/dev/random"),
+                new File("/dev/snfc_cen"),    // b/11142586
+                new File("/dev/snfc_hsel"),   // b/11142586
+                new File("/dev/snfc_intu_poll"), // b/11142586
+                new File("/dev/snfc_rfs"),    // b/11142586
+                new File("/dev/tegra-throughput"),
+                new File("/dev/tiler"),       // b/9108170
+                new File("/dev/tty"),
+                new File("/dev/urandom"),
+                new File("/dev/ump"),         // b/11142586
+                new File("/dev/xt_qtaguid"),  // b/9088251
+                new File("/dev/zero"),
+                new File("/dev/fimg2d"),      // b/10428016
+                new File("/dev/mobicore-user") // b/10428016
+            ));
+
+    public void testAllCharacterDevicesAreSecure() throws Exception {
+        Set<File> insecure = getAllInsecureDevicesInDirAndSubdir(new File("/dev"), FileUtils.S_IFCHR);
+        Set<File> insecurePts = getAllInsecureDevicesInDirAndSubdir(new File("/dev/pts"), FileUtils.S_IFCHR);
+        insecure.removeAll(CHAR_DEV_EXCEPTIONS);
+        insecure.removeAll(insecurePts);
+        assertTrue("Found insecure character devices: " + insecure.toString(),
+                insecure.isEmpty());
+    }
+
     public void testDevRandomWorldReadableAndWritable() throws Exception {
         File f = new File("/dev/random");
 
@@ -898,6 +984,67 @@
                 .fileHasOnly("/system/bin/run-as"));
     }
 
+    private static Set<File>
+    getAllInsecureDevicesInDirAndSubdir(File dir, int type) throws Exception {
+        assertTrue(dir.isDirectory());
+        Set<File> retval = new HashSet<File>();
+
+        if (isSymbolicLink(dir)) {
+            // don't examine symbolic links.
+            return retval;
+        }
+
+        File[] subDirectories = dir.listFiles(new FileFilter() {
+            @Override public boolean accept(File pathname) {
+                return pathname.isDirectory();
+            }
+        });
+
+
+        /* recurse into subdirectories */
+        if (subDirectories != null) {
+            for (File f : subDirectories) {
+                retval.addAll(getAllInsecureDevicesInDirAndSubdir(f, type));
+            }
+        }
+
+        File[] filesInThisDirectory = dir.listFiles();
+        if (filesInThisDirectory == null) {
+            return retval;
+        }
+
+        for (File f: filesInThisDirectory) {
+            FileUtils.FileStatus status = new FileUtils.FileStatus();
+            FileUtils.getFileStatus(f.getAbsolutePath(), status, false);
+            if (status.isOfType(type)) {
+                if (f.canRead() || f.canWrite() || f.canExecute()) {
+                    retval.add(f);
+                }
+                if (status.uid == 2000) {
+                    // The shell user should not own any devices
+                    retval.add(f);
+                }
+
+                // Don't allow devices owned by GIDs
+                // accessible to non-privileged applications.
+                if ((status.gid == 1007)           // AID_LOG
+                          || (status.gid == 1015)  // AID_SDCARD_RW
+                          || (status.gid == 1023)  // AID_MEDIA_RW
+                          || (status.gid == 1028)  // AID_SDCARD_R
+                          || (status.gid == 2000)) // AID_SHELL
+                {
+                    if (status.hasModeFlag(FileUtils.S_IRGRP)
+                            || status.hasModeFlag(FileUtils.S_IWGRP)
+                            || status.hasModeFlag(FileUtils.S_IXGRP))
+                    {
+                        retval.add(f);
+                    }
+                }
+            }
+        }
+        return retval;
+    }
+
     private Set<File> getWritableDirectoriesAndSubdirectoriesOf(File dir) throws Exception {
         Set<File> retval = new HashSet<File>();
         if (!dir.isDirectory()) {
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index b471286..9c997f2 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1757,14 +1757,6 @@
     <permission android:name="android.permission.SET_TIME"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Allows applications to set the system time zone.
-         <p>Protection level: normal
-    -->
-    <permission android:name="android.permission.SET_TIME_ZONE"
-        android:label="@string/permlab_setTimeZone"
-        android:description="@string/permdesc_setTimeZone"
-        android:protectionLevel="normal" />
-
     <!-- ==================================================== -->
     <!-- Permissions related to changing status bar   -->
     <!-- ==================================================== -->
@@ -3363,7 +3355,7 @@
         </service>
 
         <service
-            android:name="com.android.server.pm.BackgroundDexOptService"
+            android:name="com.android.server.BackgroundDexOptJobService"
             android:exported="true"
             android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
diff --git a/tests/tests/security/res/raw/bug_33897722.gif b/tests/tests/security/res/raw/bug_33897722.gif
new file mode 100755
index 0000000..7a563d7
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_33897722.gif
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index c1f59ae..fc2a342 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -36,7 +36,6 @@
       "69:69:56:2E:40:80:F4:24:A1:E7:19:9F:14:BA:F3:EE:58:AB:6A:BB",
       "92:5A:8F:8D:2C:6D:04:E0:66:5F:59:6A:FF:22:D8:63:E8:25:6F:3F",
       "75:E0:AB:B6:13:85:12:27:1C:04:F8:5F:DD:DE:38:E4:B7:24:2E:FE",
-      "40:9D:4B:D9:17:B5:5C:27:B6:9B:64:CB:98:22:44:0D:CD:09:B8:89",
       "E1:9F:E3:0E:8B:84:60:9E:80:9B:17:0D:72:A8:C5:BA:6E:14:09:BD",
       "DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13",
       "4F:99:AA:93:FB:2B:D1:37:26:A1:99:4A:CE:7F:F0:05:F2:93:5D:1E",
@@ -82,7 +81,6 @@
       "1B:8E:EA:57:96:29:1A:C9:39:EA:B8:0A:81:1A:73:73:C0:93:79:67",
       "6E:26:64:F3:56:BF:34:55:BF:D1:93:3F:7C:01:DE:D8:13:DA:8A:A6",
       "A9:E9:78:08:14:37:58:88:F2:05:19:B0:6D:2B:0D:2B:60:16:90:7D",
-      "60:D6:89:74:B5:C2:65:9E:8A:0F:C1:88:7C:88:D2:46:69:1B:18:2C",
       "D8:EB:6B:41:51:92:59:E0:F3:E7:85:00:C0:3D:B6:88:97:C9:EE:FC",
       "66:31:BF:9E:F7:4F:9E:B6:C9:D5:A6:0C:BA:6A:BE:D1:F7:BD:EF:7B",
       "DE:3F:40:BD:50:93:D3:9B:6C:60:F6:DA:BC:07:62:01:00:89:76:C9",
@@ -96,16 +94,15 @@
       "05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43",
       "62:52:DC:40:F7:11:43:A2:2F:DE:9E:F7:34:8E:06:42:51:B1:81:18",
       "70:17:9B:86:8C:00:A4:FA:60:91:52:22:3F:9F:3E:32:BD:E0:05:62",
-      "A0:A1:AB:90:C9:FC:84:7B:3B:12:61:E8:97:7D:5F:D3:22:61:D3:CC",
       "D1:EB:23:A4:6D:17:D6:8F:D9:25:64:C2:F1:F1:60:17:64:D8:E3:49",
       "B8:01:86:D1:EB:9C:86:A5:41:04:CF:30:54:F3:4C:52:B7:E5:58:C6",
       "2E:14:DA:EC:28:F0:FA:1E:8E:38:9A:4E:AB:EB:26:C0:0A:D3:83:C3",
       "DE:28:F4:A4:FF:E5:B9:2F:A3:C5:03:D1:A3:49:A7:F9:96:2A:82:12",
+      "0D:44:DD:8C:3C:8C:1A:1A:58:75:64:81:E9:0F:2E:2A:FF:B3:D2:6E",
       "CA:3A:FB:CF:12:40:36:4B:44:B2:16:20:88:80:48:39:19:93:7C:F7",
       "13:2D:0D:45:53:4B:69:97:CD:B2:D5:C3:39:E2:55:76:60:9B:5C:C6",
       "5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
       "49:0A:75:74:DE:87:0A:47:FE:58:EE:F6:C7:6B:EB:C6:0B:12:40:99",
-      "25:01:90:19:CF:FB:D9:99:1C:B7:68:25:74:8D:94:5F:30:93:95:42",
       "B5:1C:06:7C:EE:2B:0C:3D:F8:55:AB:2D:92:F4:FE:39:D4:E7:0F:0E",
       "29:36:21:02:8B:20:ED:02:F5:66:C5:32:D1:D6:ED:90:9F:45:00:2F",
       "37:9A:19:7B:41:85:45:35:0C:A6:03:69:F3:3C:2E:AF:47:4F:20:79",
@@ -140,12 +137,14 @@
       "20:D8:06:40:DF:9B:25:F5:12:25:3A:11:EA:F7:59:8A:EB:14:B5:47",
       "CF:9E:87:6D:D3:EB:FC:42:26:97:A3:B5:A3:7A:A0:76:A9:06:23:48",
       "2B:B1:F5:3E:55:0C:1D:C5:F1:D4:E6:B7:6A:46:4B:55:06:02:AC:21",
+      "EC:50:35:07:B2:15:C4:95:62:19:E2:A8:9A:5B:42:99:2C:4C:2C:20",
       "47:BE:AB:C9:22:EA:E8:0E:78:78:34:62:A7:9F:45:C2:54:FD:E6:8B",
       "3A:44:73:5A:E5:81:90:1F:24:86:61:46:1E:3B:9C:C4:5F:F5:3A:1B",
       "B3:1E:B1:B7:40:E3:6C:84:02:DA:DC:37:D4:4D:F5:D4:67:49:52:F9",
       "F5:17:A2:4F:9A:48:C6:C9:F8:A2:00:26:9F:DC:0F:48:2C:AB:30:89",
       "3B:C0:38:0B:33:C3:F6:A6:0C:86:15:22:93:D9:DF:F5:4B:81:C0:04",
       "03:9E:ED:B8:0B:E7:A0:3C:69:53:89:3B:20:D2:D9:32:3A:4C:2A:FD",
+      "1E:0E:56:19:0A:D1:8B:25:98:B2:04:44:FF:66:8A:04:17:99:5F:3F",
       "DF:3C:24:F9:BF:D6:66:76:1B:26:80:73:FE:06:D1:CC:8D:4F:82:A4",
       "51:C6:E7:08:49:06:6E:F3:92:D4:5C:A0:0D:6D:A3:62:8F:C3:52:39",
       "D3:DD:48:3E:2B:BF:4C:05:E8:AF:10:F5:FA:76:26:CF:D3:DC:30:92",
@@ -154,6 +153,7 @@
       "59:0D:2D:7D:88:4F:40:2E:61:7E:A5:62:32:17:65:CF:17:D8:94:E9",
       "AE:C5:FB:3F:C8:E1:BF:C4:E5:4F:03:07:5A:9A:E8:00:B7:F7:B6:FA",
       "DF:71:7E:AA:4A:D9:4E:C9:55:84:99:60:2D:48:DE:5F:BC:F0:3A:25",
+      "F6:10:84:07:D6:F8:BB:67:98:0C:C2:E2:44:C2:EB:AE:1C:EF:63:BE",
       "AF:E5:D2:44:A8:D1:19:42:30:FF:47:9F:E2:F8:97:BB:CD:7A:8C:B4",
       "D2:7A:D2:BE:ED:94:C0:A1:3C:C7:25:21:EA:5D:71:BE:81:19:F3:2B",
       "5F:3B:8C:F2:F8:10:B3:7D:78:B4:CE:EC:19:19:C3:73:34:B9:C7:74",
@@ -175,10 +175,10 @@
       "6E:3A:55:A4:19:0C:19:5C:93:84:3C:C0:DB:72:2E:31:30:61:F0:B1",
       "31:F1:FD:68:22:63:20:EE:C6:3B:3F:9D:EA:4A:3E:53:7C:7C:39:17",
       "23:88:C9:D3:71:CC:9E:96:3D:FF:7D:3C:A7:CE:FC:D6:25:EC:19:0D",
-      "8C:96:BA:EB:DD:2B:07:07:48:EE:30:32:66:A0:F3:98:6E:7C:AE:58",
       "7F:8A:B0:CF:D0:51:87:6A:66:F3:36:0F:47:C8:8D:8C:D3:35:FC:74",
       "4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5",
-      "A0:73:E5:C5:BD:43:61:0D:86:4C:21:13:0A:85:58:57:CC:9C:EA:46",
+      "5A:8C:EF:45:D7:A6:98:59:76:7A:8C:8B:44:96:B5:78:CF:47:4B:1A",
+      "8D:A7:F9:65:EC:5E:FC:37:91:0F:1C:6E:59:FD:C1:CC:6A:6E:DE:16",
       "B1:2E:13:63:45:86:A4:6F:1A:B2:60:68:37:58:2D:C4:AC:FD:94:97",
       "04:83:ED:33:99:AC:36:08:05:87:22:ED:BC:5E:46:00:E3:BE:F9:D7",
   };
diff --git a/tests/tests/security/src/android/security/cts/EncryptionTest.java b/tests/tests/security/src/android/security/cts/EncryptionTest.java
index d849126..1a49d7f 100644
--- a/tests/tests/security/src/android/security/cts/EncryptionTest.java
+++ b/tests/tests/security/src/android/security/cts/EncryptionTest.java
@@ -16,12 +16,13 @@
 
 package android.security.cts;
 
+import com.android.compatibility.common.util.PropertyUtil;
+
 import android.test.AndroidTestCase;
 import junit.framework.TestCase;
 
 import android.app.ActivityManager;
 import android.content.Context;
-import android.os.SystemProperties;
 import android.util.Log;
 import java.io.BufferedReader;
 import java.io.FileReader;
@@ -34,7 +35,7 @@
         System.loadLibrary("ctssecurity_jni");
     }
 
-    private static final int min_api_level = 23;
+    private static final int MIN_API_LEVEL = 23;
 
     private static final String TAG = "EncryptionTest";
 
@@ -50,15 +51,8 @@
     }
 
     private boolean isRequired() {
-        int first_api_level =
-            SystemProperties.getInt("ro.product.first_api_level", 0);
-
-        // Optional before min_api_level or if the device has low RAM
-        if (first_api_level > 0 && first_api_level < min_api_level) {
-            return false;
-        } else {
-            return !hasLowRAM();
-        }
+        // Optional before MIN_API_LEVEL or if the device has low RAM
+        return PropertyUtil.getFirstApiLevel() >= MIN_API_LEVEL && !hasLowRAM();
     }
 
     public void testEncryption() throws Exception {
diff --git a/tests/tests/security/src/android/security/cts/Movie33897722.java b/tests/tests/security/src/android/security/cts/Movie33897722.java
new file mode 100644
index 0000000..f6859da
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/Movie33897722.java
@@ -0,0 +1,65 @@
+/*
+ * 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.security.cts;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Movie;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.test.AndroidTestCase;
+
+import java.io.InputStream;
+
+import android.security.cts.R;
+
+public class Movie33897722 extends AndroidTestCase {
+    /**
+     * Verifies that decoding a particular GIF file does not read out out of bounds.
+     *
+     * The image has a color map of size 2, but states that pixels should come from values
+     * larger than 2. Ensure that we do not attempt to read colors from beyond the end of the
+     * color map, which would be reading memory that we do not control, and may be uninitialized.
+     */
+    public void test_android_bug_33897722() {
+        InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_33897722);
+        Movie movie = Movie.decodeStream(exploitImage);
+        assertNotNull(movie);
+        assertEquals(movie.width(), 600);
+        assertEquals(movie.height(), 752);
+
+        // The image has a 10 x 10 frame on top of a transparent background. Only test the
+        // 10 x 10 frame, since the original bug would never have used uninitialized memory
+        // outside of it.
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        // Use Src PorterDuff mode, to see exactly what the Movie creates.
+        Paint paint = new Paint();
+        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+
+        movie.draw(canvas, 0, 0, paint);
+
+        for (int x = 0; x < 10; x++) {
+            for (int y = 0; y < 10; y++) {
+                assertEquals(bitmap.getPixel(x, y), Color.TRANSPARENT);
+            }
+        }
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/NativeCodeTest.java b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
index 5fa698e..eb162fb 100644
--- a/tests/tests/security/src/android/security/cts/NativeCodeTest.java
+++ b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
@@ -16,6 +16,8 @@
 
 package android.security.cts;
 
+import android.platform.test.annotations.SecurityTest;
+
 import junit.framework.TestCase;
 
 public class NativeCodeTest extends TestCase {
@@ -24,6 +26,7 @@
         System.loadLibrary("ctssecurity_jni");
     }
 
+    @SecurityTest
     public void testVroot() throws Exception {
         assertTrue("Device is vulnerable to CVE-2013-6282. Please apply security patch at "
                    + "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/"
@@ -31,6 +34,7 @@
                    + "8404663f81d212918ff85f493649a7991209fa04", doVrootTest());
     }
 
+    @SecurityTest
     public void testPerfEvent() throws Exception {
         assertFalse("Device is vulnerable to CVE-2013-2094. Please apply security patch "
                     + "at http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/"
@@ -38,10 +42,12 @@
                     doPerfEventTest());
     }
 
+    @SecurityTest
     public void testPerfEvent2() throws Exception {
         assertTrue(doPerfEventTest2());
     }
 
+    @SecurityTest
     public void testFutex() throws Exception {
         assertTrue("Device is vulnerable to CVE-2014-3153, a vulnerability in the futex() system "
                    + "call. Please apply the security patch at "
@@ -50,6 +56,7 @@
                    doFutexTest());
     }
 
+    @SecurityTest
     public void testNvmapIocFromId() throws Exception {
         assertTrue("Device is vulnerable to CVE-2014-5332. "
                    + "NVIDIA has released code fixes to upstream repositories and device vendors. "
@@ -58,6 +65,7 @@
                    doNvmapIocFromIdTest());
     }
 
+    @SecurityTest
     public void testPingPongRoot() throws Exception {
         assertTrue("Device is vulnerable to CVE-2015-3636, a vulnerability in the ping "
                    + "socket implementation. Please apply the security patch at "
@@ -65,6 +73,7 @@
                    doPingPongRootTest());
     }
 
+    @SecurityTest
     public void testPipeReadV() throws Exception {
         assertTrue("Device is vulnerable to CVE-2015-1805 and/or CVE-2016-0774,"
                    + " a vulnerability in the pipe_read() function."
@@ -74,6 +83,7 @@
                    doPipeReadVTest());
     }
 
+    @SecurityTest
     public void testSysVipc() throws Exception {
         assertTrue("Android does not support Sys V IPC, it must "
                    + "be removed from the kernel. In the kernel config: "
@@ -114,6 +124,7 @@
      */
     private static native boolean doVrootTest();
 
+    @SecurityTest
     public void testCVE20141710() throws Exception {
         assertTrue("Device is vulnerable to CVE-2014-1710", doCVE20141710Test());
     }
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index cb4dd1f..fd7c27d 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -70,74 +70,92 @@
      before any existing test methods
      ***********************************************************/
 
+    @SecurityTest
     public void testStagefright_bug_33137046() throws Exception {
         doStagefrightTest(R.raw.bug_33137046);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2016_2507() throws Exception {
         doStagefrightTest(R.raw.cve_2016_2507);
     }
 
+    @SecurityTest
     public void testStagefright_bug_31647370() throws Exception {
         doStagefrightTest(R.raw.bug_31647370);
     }
 
+    @SecurityTest
     public void testStagefright_bug_32577290() throws Exception {
         doStagefrightTest(R.raw.bug_32577290);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_1538_1() throws Exception {
         doStagefrightTest(R.raw.cve_2015_1538_1);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_1538_2() throws Exception {
         doStagefrightTest(R.raw.cve_2015_1538_2);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_1538_3() throws Exception {
         doStagefrightTest(R.raw.cve_2015_1538_3);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_1538_4() throws Exception {
         doStagefrightTest(R.raw.cve_2015_1538_4);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_1539() throws Exception {
         doStagefrightTest(R.raw.cve_2015_1539);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3824() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3824);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3826() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3826);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3827() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3827);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3828() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3828);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3829() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3829);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3864() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3864);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_6598() throws Exception {
         doStagefrightTest(R.raw.cve_2015_6598);
     }
 
+    @SecurityTest
     public void testStagefright_bug_26366256() throws Exception {
         doStagefrightTest(R.raw.bug_26366256);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2016_2429_b_27211885() throws Exception {
         doStagefrightTest(R.raw.cve_2016_2429_b_27211885);
     }
@@ -147,38 +165,47 @@
      before any existing test methods
      ***********************************************************/
 
+    @SecurityTest
     public void testStagefright_bug_32873375() throws Exception {
         doStagefrightTest(R.raw.bug_32873375);
     }
 
+    @SecurityTest
     public void testStagefright_bug_25765591() throws Exception {
         doStagefrightTest(R.raw.bug_25765591);
     }
 
+    @SecurityTest
     public void testStagefright_bug_25812590() throws Exception {
         doStagefrightTest(R.raw.bug_25812590);
     }
 
+    @SecurityTest
     public void testStagefright_bug_26070014() throws Exception {
         doStagefrightTest(R.raw.bug_26070014);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3867() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3867);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3869() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3869);
     }
 
+    @SecurityTest
     public void testStagefright_bug_32322258() throws Exception {
         doStagefrightTest(R.raw.bug_32322258);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3873_b_23248776() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3873_b_23248776);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3873_b_20718524() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3873_b_20718524);
     }
@@ -193,30 +220,37 @@
         doStagefrightTest(R.raw.cve_2015_3867_b_23213430);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2015_3873_b_21814993() throws Exception {
         doStagefrightTest(R.raw.cve_2015_3873_b_21814993);
     }
 
+    @SecurityTest
     public void testStagefright_bug_32915871() throws Exception {
         doStagefrightTest(R.raw.bug_32915871);
     }
 
+    @SecurityTest
     public void testStagefright_bug_28333006() throws Exception {
         doStagefrightTest(R.raw.bug_28333006);
     }
 
+    @SecurityTest
     public void testStagefright_bug_14388161() throws Exception {
         doStagefrightTestMediaPlayer(R.raw.bug_14388161);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2016_3755() throws Exception {
         doStagefrightTest(R.raw.cve_2016_3755);
     }
 
+    @SecurityTest
     public void testStagefright_cve_2016_3878_b_29493002() throws Exception {
         doStagefrightTest(R.raw.cve_2016_3878_b_29493002);
     }
 
+    @SecurityTest
     public void testStagefright_bug_27855419_CVE_2016_2463() throws Exception {
         doStagefrightTest(R.raw.bug_27855419);
     }
diff --git a/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java b/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
index fc98adc1..151ccf2 100644
--- a/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
+++ b/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
@@ -18,6 +18,7 @@
 import junit.framework.TestCase;
 
 import android.content.Context;
+import android.platform.test.annotations.SecurityTest;
 import android.media.audiofx.AudioEffect;
 import android.media.MediaPlayer;
 import android.media.audiofx.Visualizer;
@@ -36,6 +37,7 @@
     }
 
     //Testing security bug: 30229821
+    @SecurityTest
     public void testVisualizer_MalformedConstructor() throws Exception {
         final String VISUALIZER_TYPE = "e46b26a0-dddd-11db-8afd-0002a5d5c51b";
         final int VISUALIZER_CMD_MEASURE = 0x10001;
diff --git a/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java b/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
index f81da6b..bbc70a9 100644
--- a/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
+++ b/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.platform.test.annotations.SecurityTest;
 import android.test.AndroidTestCase;
 
 import java.io.InputStream;
@@ -31,6 +32,7 @@
      * Prior to fixing bug 33300701, decoding resulted in undefined behavior (divide by zero).
      * With the fix, decoding will fail, without dividing by zero.
      */
+    @SecurityTest
     public void test_android_bug_33300701() {
         InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_33300701);
         Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
index a040715..aeaf06b 100644
--- a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
@@ -16,6 +16,8 @@
 package android.content.pm.cts.shortcutmanager.throttling;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,6 +31,8 @@
  * Make sure that when a fg service is running, shortcut manager calls are not throttled.
  */
 public class FgService extends Service {
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/shortcutmanager/FgService";
+
     public static void start(Context context, String replyAction) {
         final Intent i =
                 new Intent().setComponent(new ComponentName(context, FgService.class))
@@ -40,10 +44,15 @@
     public int onStartCommand(Intent intent, int flags, int startId) {
 
         // Start as foreground.
-        Notification notification = new Notification.Builder(getApplicationContext())
-                .setContentTitle("FgService")
-                .setSmallIcon(android.R.drawable.ic_popup_sync)
-                .build();
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        notificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
+        Notification notification =
+                new Notification.Builder(getApplicationContext(), NOTIFICATION_CHANNEL_ID)
+                        .setContentTitle("FgService")
+                        .setSmallIcon(android.R.drawable.ic_popup_sync)
+                        .build();
         startForeground(1, notification);
 
         final String replyAction = intent.getStringExtra(Constants.EXTRA_REPLY_ACTION);
diff --git a/tests/tests/telecom/AndroidManifest.xml b/tests/tests/telecom/AndroidManifest.xml
index 73ba945..eabe292 100644
--- a/tests/tests/telecom/AndroidManifest.xml
+++ b/tests/tests/telecom/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
     <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
@@ -41,6 +42,13 @@
             </intent-filter>
         </service>
 
+        <service android:name="android.telecom.cts.CtsSelfManagedConnectionService"
+            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+            <intent-filter>
+                <action android:name="android.telecom.ConnectionService" />
+            </intent-filter>
+        </service>
+
         <service android:name="android.telecom.cts.MockInCallService"
             android:permission="android.permission.BIND_INCALL_SERVICE" >
             <intent-filter>
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index a6e92c0..86832eb 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -15,8 +15,16 @@
  */
 
 package android.telecom.cts;
-
-import static android.telecom.cts.TestUtils.*;
+import static android.telecom.cts.TestUtils.ACCOUNT_ID;
+import static android.telecom.cts.TestUtils.ACCOUNT_LABEL;
+import static android.telecom.cts.TestUtils.COMPONENT;
+import static android.telecom.cts.TestUtils.PACKAGE;
+import static android.telecom.cts.TestUtils.SELF_MANAGED_ACCOUNT_ID_1;
+import static android.telecom.cts.TestUtils.SELF_MANAGED_ACCOUNT_ID_2;
+import static android.telecom.cts.TestUtils.SELF_MANAGED_ACCOUNT_LABEL;
+import static android.telecom.cts.TestUtils.SELF_MANAGED_COMPONENT;
+import static android.telecom.cts.TestUtils.TAG;
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
 
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -47,6 +55,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 
 /**
  * Base class for Telecom CTS tests that require a {@link CtsConnectionService} and
@@ -59,7 +68,6 @@
 
     public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE =
             new PhoneAccountHandle(new ComponentName(PACKAGE, COMPONENT), ACCOUNT_ID);
-
     public static final PhoneAccount TEST_PHONE_ACCOUNT = PhoneAccount.builder(
             TEST_PHONE_ACCOUNT_HANDLE, ACCOUNT_LABEL)
             .setAddress(Uri.parse("tel:555-TEST"))
@@ -73,6 +81,38 @@
             .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
             .build();
 
+    public static final PhoneAccountHandle TEST_SELF_MANAGED_HANDLE_1 =
+            new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT),
+                    SELF_MANAGED_ACCOUNT_ID_1);
+    public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_1 = PhoneAccount.builder(
+            TEST_SELF_MANAGED_HANDLE_1, SELF_MANAGED_ACCOUNT_LABEL)
+            .setAddress(Uri.parse("sip:test@test.com"))
+            .setSubscriptionAddress(Uri.parse("sip:test@test.com"))
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                    PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING |
+                    PhoneAccount.CAPABILITY_VIDEO_CALLING)
+            .setHighlightColor(Color.BLUE)
+            .setShortDescription(SELF_MANAGED_ACCOUNT_LABEL)
+            .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+            .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
+            .build();
+
+    public static final PhoneAccountHandle TEST_SELF_MANAGED_HANDLE_2 =
+            new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT),
+                    SELF_MANAGED_ACCOUNT_ID_2);
+    public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_2 = PhoneAccount.builder(
+            TEST_SELF_MANAGED_HANDLE_2, SELF_MANAGED_ACCOUNT_LABEL)
+            .setAddress(Uri.parse("sip:test@test.com"))
+            .setSubscriptionAddress(Uri.parse("sip:test@test.com"))
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                    PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING |
+                    PhoneAccount.CAPABILITY_VIDEO_CALLING)
+            .setHighlightColor(Color.BLUE)
+            .setShortDescription(SELF_MANAGED_ACCOUNT_LABEL)
+            .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+            .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
+            .build();
+
     private static int sCounter = 5549999;
 
     Context mContext;
@@ -101,7 +141,7 @@
         mContext = getInstrumentation().getContext();
         mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
 
-        mShouldTestTelecom = shouldTestTelecom(mContext);
+        mShouldTestTelecom = TestUtils.shouldTestTelecom(mContext);
         if (mShouldTestTelecom) {
             mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
             TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
@@ -857,6 +897,27 @@
         );
     }
 
+    /**
+     * Checks all fields of two PhoneAccounts for equality, with the exception of the enabled state.
+     * Should only be called after assertPhoneAccountRegistered when it can be guaranteed
+     * that the PhoneAccount is registered.
+     * @param expected The expected PhoneAccount.
+     * @param actual The actual PhoneAccount.
+     */
+    void assertPhoneAccountEquals(final PhoneAccount expected,
+            final PhoneAccount actual) {
+        assertEquals(expected.getAddress(), actual.getAddress());
+        assertEquals(expected.getAccountHandle(), actual.getAccountHandle());
+        assertEquals(expected.getCapabilities(), actual.getCapabilities());
+        assertTrue(areBundlesEqual(expected.getExtras(), actual.getExtras()));
+        assertEquals(expected.getHighlightColor(), actual.getHighlightColor());
+        assertEquals(expected.getIcon(), actual.getIcon());
+        assertEquals(expected.getLabel(), actual.getLabel());
+        assertEquals(expected.getShortDescription(), actual.getShortDescription());
+        assertEquals(expected.getSubscriptionAddress(), actual.getSubscriptionAddress());
+        assertEquals(expected.getSupportedUriSchemes(), actual.getSupportedUriSchemes());
+    }
+
     void assertPhoneAccountRegistered(final PhoneAccountHandle handle) {
         waitUntilConditionIsTrueOrTimeout(
                 new Condition() {
@@ -930,6 +991,24 @@
         );
     }
 
+    void assertIsInCall(boolean isIncall) {
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return isIncall;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return mTelecomManager.isInCall();
+                    }
+                },
+                WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+                "Expected isInCall to be " + isIncall
+        );
+    }
+
     /**
      * Asserts that a call's properties are as expected.
      *
@@ -1095,6 +1174,39 @@
                 }
             }
         }
+
+        /**
+         * Waits for a predicate to return {@code true} within the specified timeout.  Uses the
+         * {@link #mLock} for this {@link InvokeCounter} to eliminate the need to perform busy-wait.
+         * @param predicate The predicate.
+         * @param timeoutMillis The timeout.
+         */
+        public void waitForPredicate(Predicate predicate, long timeoutMillis) {
+            synchronized (mLock) {
+                mInvokeArgs.clear();
+                long startTimeMillis = SystemClock.uptimeMillis();
+                long elapsedTimeMillis = 0;
+                long remainingTimeMillis = timeoutMillis;
+                Object foundValue = null;
+                boolean wasFound = false;
+                do {
+                    try {
+                        mLock.wait(timeoutMillis);
+                        foundValue = (mInvokeArgs.get(mInvokeArgs.size()-1))[0];
+                        wasFound = predicate.test(foundValue);
+                        elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+                        remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+                    } catch (InterruptedException ie) {
+                        /* ignore */
+                    }
+                } while (!wasFound && remainingTimeMillis > 0);
+                if (wasFound) {
+                    return;
+                } else if (remainingTimeMillis <= 0) {
+                    fail("Expected value not found within time limit");
+                }
+            }
+        }
     }
 
     public static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
new file mode 100644
index 0000000..ee2f842
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
@@ -0,0 +1,183 @@
+/*
+ * 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.telecom.cts;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.ConnectionService;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * CTS test self-managed {@link ConnectionService} implementation.
+ */
+public class CtsSelfManagedConnectionService extends ConnectionService {
+    // Constants used to address into the mLocks array.
+    public static int CONNECTION_CREATED_LOCK = 0;
+    public static int CREATE_INCOMING_CONNECTION_FAILED_LOCK = 1;
+    public static int CREATE_OUTGOING_CONNECTION_FAILED_LOCK = 2;
+    private static int NUM_LOCKS = CREATE_OUTGOING_CONNECTION_FAILED_LOCK + 1;
+
+    private static CtsSelfManagedConnectionService sConnectionService;
+
+    // Lock used to determine when binding to CS is complete.
+    private static CountDownLatch sBindingLock = new CountDownLatch(1);
+
+    private SelfManagedConnection.Listener mConnectionListener =
+            new SelfManagedConnection.Listener() {
+        @Override
+        void onDestroyed(SelfManagedConnection connection) {
+            mConnections.remove(connection);
+        }
+    };
+
+    private CountDownLatch[] mLocks = new CountDownLatch[NUM_LOCKS];
+
+    private List<SelfManagedConnection> mConnections = new ArrayList<>();
+
+    public static CtsSelfManagedConnectionService getConnectionService() {
+        return sConnectionService;
+    }
+
+    public CtsSelfManagedConnectionService() throws Exception {
+        super();
+        sConnectionService = this;
+        Arrays.setAll(mLocks, i -> new CountDownLatch(1));
+
+        // Inform anyone waiting on binding that we're bound.
+        sBindingLock.countDown();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        sBindingLock = new CountDownLatch(1);
+        return super.onUnbind(intent);
+    }
+
+    @Override
+    public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount,
+            final ConnectionRequest request) {
+
+        return createSelfManagedConnection(request, false);
+    }
+
+    @Override
+    public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request) {
+
+        return createSelfManagedConnection(request, true);
+    }
+
+    @Override
+    public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
+        mLocks[CREATE_INCOMING_CONNECTION_FAILED_LOCK].countDown();
+    }
+
+    @Override
+    public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+        mLocks[CREATE_OUTGOING_CONNECTION_FAILED_LOCK].countDown();
+    }
+
+    public void tearDown() {
+        if (mConnections != null && mConnections.size() > 0) {
+            mConnections.forEach(connection -> {
+                        connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+                        connection.destroy();
+                    }
+            );
+            mConnections.clear();
+        }
+        sBindingLock = new CountDownLatch(1);
+    }
+
+    private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming) {
+        SelfManagedConnection connection = new SelfManagedConnection(isIncoming,
+                mConnectionListener);
+        connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
+        connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
+        connection.setExtras(request.getExtras());
+
+        Bundle moreExtras = new Bundle();
+        moreExtras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                request.getAccountHandle());
+        connection.putExtras(moreExtras);
+        connection.setVideoState(request.getVideoState());
+
+        mConnections.add(connection);
+        mLocks[CONNECTION_CREATED_LOCK].countDown();
+        return connection;
+    }
+
+    public List<SelfManagedConnection> getConnections() {
+        return mConnections;
+    }
+
+    /**
+     * Waits on a lock for maximum of 5 seconds.
+     *
+     * @param lock one of the {@code *_LOCK} constants defined above.
+     * @return {@code true} if the lock was released within the time limit, {@code false} if the
+     *      timeout expired without the lock being released.
+     */
+    public boolean waitForUpdate(int lock) {
+        mLocks[lock] = waitForLock(mLocks[lock]);
+        return mLocks[lock] != null;
+    }
+
+    /**
+     * Waits for the {@link ConnectionService} to be found.
+     * @return {@code true} if binding happened within the time limit, or {@code false} otherwise.
+     */
+    public static boolean waitForBinding() {
+        sBindingLock = waitForLock(sBindingLock);
+        return sBindingLock != null;
+    }
+
+    /**
+     * Given a {@link CountDownLatch}, wait for the latch to reach zero for 5 seconds.  If the lock
+     * was released, return a new instance.  Otherwise, return null to indicate that the timeout
+     * expired without the lock being released.
+     *
+     * @param lock The lock to wait on.
+     * @return {@code true} if the lock was released, and {@code false} if it failed to be released.
+     */
+    private static CountDownLatch waitForLock(CountDownLatch lock) {
+        boolean success;
+        try {
+            success = lock.await(5000, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException ie) {
+            return null;
+        }
+
+        if (success) {
+            return new CountDownLatch(1);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnection.java b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnection.java
new file mode 100644
index 0000000..cf1d1a2
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnection.java
@@ -0,0 +1,72 @@
+/*
+ * 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.telecom.cts;
+
+import android.telecom.CallAudioState;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.cts.BaseTelecomTestWithMockServices.InvokeCounter;
+
+/**
+ * CTS Test self-managed {@link Connection} implementation.
+ */
+public class SelfManagedConnection extends Connection {
+
+    InvokeCounter mCallAudioRouteInvokeCounter = new InvokeCounter("onCallAudioStateChanged");
+    InvokeCounter mOnShowIncomingUiInvokeCounter = new InvokeCounter(
+            "onShowIncomingUiInvokeCounter");
+
+    public static abstract class Listener {
+        void onDestroyed(SelfManagedConnection connection) { };
+    }
+
+    private final boolean mIsIncomingCall;
+    private final Listener mListener;
+
+    public SelfManagedConnection(boolean isIncomingCall, Listener listener) {
+        mIsIncomingCall = isIncomingCall;
+        mListener = listener;
+    }
+
+    public boolean isIncomingCall() {
+        return mIsIncomingCall;
+    }
+
+    public void disconnectAndDestroy() {
+        setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+        destroy();
+        mListener.onDestroyed(this);
+    }
+
+    @Override
+    public void onCallAudioStateChanged(CallAudioState state) {
+        mCallAudioRouteInvokeCounter.invoke(state);
+    }
+
+    @Override
+    public void onShowIncomingCallUi() {
+        mOnShowIncomingUiInvokeCounter.invoke();
+    }
+
+    public InvokeCounter getCallAudioStateChangedInvokeCounter() {
+        return mCallAudioRouteInvokeCounter;
+    }
+
+    public InvokeCounter getOnShowIncomingUiInvokeCounter() {
+        return mOnShowIncomingUiInvokeCounter;
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
new file mode 100644
index 0000000..d9bdfa8
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
@@ -0,0 +1,453 @@
+/*
+ * 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.telecom.cts;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.CallAudioState;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
+
+/**
+ * CTS tests for the self-managed {@link android.telecom.ConnectionService} APIs.
+ * For more information about these APIs, see {@link android.telecom}, and
+ * {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED}.
+ */
+
+public class SelfManagedConnectionServiceTest extends BaseTelecomTestWithMockServices {
+    private Uri TEST_ADDRESS_1 = Uri.fromParts("sip", "call1@test.com", null);
+    private Uri TEST_ADDRESS_2 = Uri.fromParts("sip", "call2@test.com", null);
+    private Uri TEST_ADDRESS_3 = Uri.fromParts("sip", "call3@test.com", null);
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getContext();
+        if (mShouldTestTelecom) {
+            // Register and enable the CTS ConnectionService; we want to be able to test a managed
+            // ConnectionService alongside a self-managed ConnectionService.
+            setupConnectionService(null, FLAG_REGISTER | FLAG_ENABLE);
+
+            mTelecomManager.registerPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT_1);
+            mTelecomManager.registerPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT_2);
+        }
+
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        CtsSelfManagedConnectionService connectionService =
+                CtsSelfManagedConnectionService.getConnectionService();
+        if (connectionService != null) {
+            connectionService.tearDown();
+        }
+    }
+
+    /**
+     * Tests the ability to successfully register a self-managed
+     * {@link android.telecom.PhoneAccount}.
+     */
+    public void testRegisterSelfManagedConnectionService() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // The phone account is registered in the setup method.
+        assertPhoneAccountRegistered(TEST_SELF_MANAGED_HANDLE_1);
+        assertPhoneAccountEnabled(TEST_SELF_MANAGED_HANDLE_1);
+        PhoneAccount registeredAccount = mTelecomManager.getPhoneAccount(TEST_SELF_MANAGED_HANDLE_1);
+
+        // It should exist and be the same as the previously registered one.
+        assertNotNull(registeredAccount);
+
+        // We cannot just check for equality of the PhoneAccount since the one we registered is not
+        // enabled, and the one we get back after registration is.
+        assertPhoneAccountEquals(TEST_SELF_MANAGED_PHONE_ACCOUNT_1, registeredAccount);
+
+        // An important asumption is that self-managed PhoneAccounts are automatically
+        // enabled by default.
+        assertTrue("Self-managed PhoneAccounts must be enabled by default.",
+                registeredAccount.isEnabled());
+    }
+
+    /**
+     * This test ensures that a {@link android.telecom.PhoneAccount} declared as self-managed cannot
+     * but is also registered as a call provider is not permitted.
+     *
+     * A self-managed {@link android.telecom.PhoneAccount} cannot also be a call provider.
+     */
+    public void testRegisterCallCapableSelfManagedConnectionService() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Attempt to register both a call provider and self-managed account.
+        PhoneAccount toRegister = TEST_SELF_MANAGED_PHONE_ACCOUNT_1.toBuilder()
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                        PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build();
+
+        registerAndExpectFailure(toRegister);
+    }
+
+    /**
+     * This test ensures that a {@link android.telecom.PhoneAccount} declared as self-managed cannot
+     * but is also registered as a sim subscription is not permitted.
+     *
+     * A self-managed {@link android.telecom.PhoneAccount} cannot also be a SIM subscription.
+     */
+    public void testRegisterSimSelfManagedConnectionService() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Attempt to register both a call provider and self-managed account.
+        PhoneAccount toRegister = TEST_SELF_MANAGED_PHONE_ACCOUNT_1.toBuilder()
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .build();
+
+        registerAndExpectFailure(toRegister);
+    }
+
+    /**
+     * This test ensures that a {@link android.telecom.PhoneAccount} declared as self-managed cannot
+     * but is also registered as a connection manager is not permitted.
+     *
+     * A self-managed {@link android.telecom.PhoneAccount} cannot also be a connection manager.
+     */
+    public void testRegisterConnectionManagerSelfManagedConnectionService() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Attempt to register both a call provider and self-managed account.
+        PhoneAccount toRegister = TEST_SELF_MANAGED_PHONE_ACCOUNT_1.toBuilder()
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                        PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+                .build();
+
+        registerAndExpectFailure(toRegister);
+    }
+
+    /**
+     * Attempts to register a {@link android.telecom.PhoneAccount}, expecting a security exception
+     * which indicates that invalid capabilities were specified.
+     *
+     * @param toRegister The PhoneAccount to register.
+     */
+    private void registerAndExpectFailure(PhoneAccount toRegister) {
+        try {
+            mTelecomManager.registerPhoneAccount(toRegister);
+        } catch (SecurityException se) {
+            assertEquals("Self-managed ConnectionServices cannot also be call capable, " +
+                    "connection managers, or SIM accounts.", se.getMessage());
+            return;
+        }
+        fail("Expected SecurityException");
+    }
+
+    /**
+     * Tests ability to add a new self-managed incoming connection.
+     */
+    public void testAddSelfManagedIncomingConnection() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        addIncomingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+
+        // Ensure Telecom bound to the self managed CS
+        if (!CtsSelfManagedConnectionService.waitForBinding()) {
+            fail("Could not bind to Self-Managed ConnectionService");
+        }
+
+        SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+
+        // Expect callback indicating that UI should be shown.
+        connection.getOnShowIncomingUiInvokeCounter().waitForCount(1);
+
+        setActiveAndVerify(connection);
+        assertMockInCallServiceUnbound();
+        setDisconnectedAndVerify(connection);
+    }
+
+    /**
+     * Tests ability to add a new self-managed outgoing connection.
+     */
+    public void testAddSelfManagedOutgoingConnection() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+
+        // Ensure Telecom bound to the self managed CS
+        if (!CtsSelfManagedConnectionService.waitForBinding()) {
+            fail("Could not bind to Self-Managed ConnectionService");
+        }
+
+        SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+        assert(!connection.isIncomingCall());
+
+        assertEquals(connection.getOnShowIncomingUiInvokeCounter().getInvokeCount(), 0);
+
+        setActiveAndVerify(connection);
+        assertMockInCallServiceUnbound();
+        setDisconnectedAndVerify(connection);
+    }
+
+    /**
+     * Tests ability to change the audio route via the
+     * {@link android.telecom.Connection#setAudioRoute(int)} API.
+     */
+    public void testAudioRoute() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+        SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+        setActiveAndVerify(connection);
+
+        InvokeCounter counter = connection.getCallAudioStateChangedInvokeCounter();
+
+        connection.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+        counter.waitForPredicate(new Predicate<CallAudioState>() {
+                @Override
+                public boolean test(CallAudioState cas) {
+                    return cas.getRoute() == CallAudioState.ROUTE_SPEAKER;
+                }
+            }, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+        connection.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
+        counter.waitForPredicate(new Predicate<CallAudioState>() {
+            @Override
+            public boolean test(CallAudioState cas) {
+                return cas.getRoute() == CallAudioState.ROUTE_EARPIECE;
+            }
+        }, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+        setDisconnectedAndVerify(connection);
+    }
+
+    /**
+     * Tests that Telecom will disallow an outgoing call when there is already an ongoing call in
+     * another third-party app.
+     * @throws Exception
+     */
+    public void testDisallowOutgoingCall() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Create an ongoing call in the first self-managed PhoneAccount.
+        placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+        SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+        setActiveAndVerify(connection);
+
+        // Attempt to create a new outgoing call for the other PhoneAccount; it should fail.
+        placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_2, TEST_ADDRESS_2);
+        assertTrue("Expected onCreateOutgoingConnectionFailed callback",
+                CtsSelfManagedConnectionService.getConnectionService().waitForUpdate(
+                    CtsSelfManagedConnectionService.CREATE_OUTGOING_CONNECTION_FAILED_LOCK));
+
+        setDisconnectedAndVerify(connection);
+    }
+
+    /**
+     * Tests that Telecom will disallow an outgoing call when there is already an ongoing call in
+     * another third-party app.
+     * @throws Exception
+     */
+    public void testIncomingWhileOngoing() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Create an ongoing call in the first self-managed PhoneAccount.
+        placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+        SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+        setActiveAndVerify(connection);
+
+        // Attempt to create a new outgoing call for the other PhoneAccount; it should succeed.
+        addIncomingCall(TEST_SELF_MANAGED_HANDLE_2, TEST_ADDRESS_2);
+        SelfManagedConnection connection2 = waitForAndGetConnection(TEST_ADDRESS_2);
+
+        connection2.disconnectAndDestroy();
+        setDisconnectedAndVerify(connection);
+    }
+
+    /**
+     * Tests that Telecom enforces a maximum number of calls for a self-managed ConnectionService.
+     *
+     * @throws Exception
+     */
+    public void testCallLimit() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        List<SelfManagedConnection> connections = new ArrayList<>();
+        // Create 10 calls; they should succeed.
+        for (int ix = 0; ix < 10; ix++) {
+            Uri address = Uri.fromParts("sip", "test" + ix + "@test.com", null);
+            // Create an ongoing call in the first self-managed PhoneAccount.
+            placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, address);
+            SelfManagedConnection connection = waitForAndGetConnection(address);
+            setActiveAndVerify(connection);
+            connections.add(connection);
+        }
+
+        // Try adding an 11th.  It should fail to be created.
+        addIncomingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_2);
+        assertTrue("Expected onCreateIncomingConnectionFailed callback",
+                CtsSelfManagedConnectionService.getConnectionService().waitForUpdate(
+                        CtsSelfManagedConnectionService.CREATE_INCOMING_CONNECTION_FAILED_LOCK));
+
+        connections.forEach((selfManagedConnection) ->
+                selfManagedConnection.disconnectAndDestroy());
+    }
+
+    public void testEmergencyCallOngoing() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Set 555-1212 as a test emergency number.
+        TestUtils.executeShellCommand(getInstrumentation(),
+                "setprop ril.ecclist 5551212");
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEST_PHONE_ACCOUNT_HANDLE);
+        mTelecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, "5551212", null), extras);
+        assertIsInCall(true);
+        try {
+            TestUtils.waitOnAllHandlers(getInstrumentation());
+        } catch (Exception e) {
+            fail("Failed to wait on handlers " + e);
+        }
+
+        // Try adding a self managed call.  It should fail to be created.
+        addIncomingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+        assertTrue("Expected onCreateIncomingConnectionFailed callback",
+                CtsSelfManagedConnectionService.getConnectionService().waitForUpdate(
+                        CtsSelfManagedConnectionService.CREATE_INCOMING_CONNECTION_FAILED_LOCK));
+    }
+
+    /**
+     * Adds a new self-managed incoming call.
+     *
+     * @param handle the PhoneAccountHandle associated with the call.
+     * @param address the incoming address.
+     * @return the new self-managed incoming call.
+     */
+    private void addIncomingCall(PhoneAccountHandle handle, Uri address) {
+        // Inform telecom of new incoming self-managed connection.
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, address);
+        mTelecomManager.addNewIncomingCall(handle, extras);
+
+        // Wait for Telecom to finish creating the new connection.
+        try {
+            TestUtils.waitOnAllHandlers(getInstrumentation());
+        } catch (Exception e) {
+            fail("Failed to wait on handlers");
+        }
+    }
+
+    /**
+     * Places a new self-managed outgoing call.
+     *
+     * @param handle the PhoneAccountHandle associated with the call.
+     * @param address outgoing call address.
+     * @return the new self-managed outgoing call.
+     */
+    private void placeOutgoingCall(PhoneAccountHandle handle, Uri address) {
+        // Inform telecom of new incoming self-managed connection.
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle);
+        mTelecomManager.placeCall(address, extras);
+
+        // Wait for Telecom to finish creating the new connection.
+        try {
+            TestUtils.waitOnAllHandlers(getInstrumentation());
+        } catch (Exception e) {
+            fail("Failed to wait on handlers");
+        }
+    }
+
+    /**
+     * Waits for a new SelfManagedConnection with the given address to be added.
+     * @param address The expected address.
+     * @return The SelfManagedConnection found.
+     */
+    public SelfManagedConnection waitForAndGetConnection(Uri address) {
+        // Wait for creation of the new connection.
+        CtsSelfManagedConnectionService connectionService =
+                CtsSelfManagedConnectionService.getConnectionService();
+        assertTrue(connectionService.waitForUpdate(
+                CtsSelfManagedConnectionService.CONNECTION_CREATED_LOCK));
+
+        Optional<SelfManagedConnection> connectionOptional = connectionService.getConnections()
+                .stream()
+                .filter(connection -> address.equals(connection.getAddress()))
+                .findFirst();
+
+        assert(connectionOptional.isPresent());
+        return connectionOptional.get();
+    }
+
+    /**
+     * Sets a connection active, verifies TelecomManager thinks we're in call, that we did not bind
+     * to the IncallService, and finally disconnects and destroys the connection..
+     * @param connection The connection.
+     */
+    private void setActiveAndVerify(SelfManagedConnection connection) throws Exception {
+        // Set the connection active.
+        connection.setActive();
+
+        // Check with Telecom if we're in a call.
+        assertIsInCall(true);
+    }
+
+    /**
+     * Sets a connection to be disconnected, and then waits until the TelecomManager reports it is
+     * no longer in a call.
+     *
+     * @param connection The connection to disconnect/destroy.
+     */
+    private void setDisconnectedAndVerify(SelfManagedConnection connection) {
+        // Now, disconnect call and clean it up.
+        connection.disconnectAndDestroy();
+
+        assertIsInCall(false);
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
index f259985..030ee77 100644
--- a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
+++ b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
@@ -41,12 +41,17 @@
     // tests in the Telecom2 test package.
     public static String PACKAGE = "android.telecom.cts";
     public static final String COMPONENT = "android.telecom.cts.CtsConnectionService";
+    public static final String SELF_MANAGED_COMPONENT =
+            "android.telecom.cts.CtsSelfManagedConnectionService";
     public static final String REMOTE_COMPONENT = "android.telecom.cts.CtsRemoteConnectionService";
     public static final String ACCOUNT_ID = "xtstest_CALL_PROVIDER_ID";
     public static final String REMOTE_ACCOUNT_ID = "xtstest_REMOTE_CALL_PROVIDER_ID";
+    public static final String SELF_MANAGED_ACCOUNT_ID_1 = "ctstest_SELF_MANAGED_ID_1";
+    public static final String SELF_MANAGED_ACCOUNT_ID_2 = "ctstest_SELF_MANAGED_ID_2";
 
     public static final String ACCOUNT_LABEL = "CTSConnectionService";
     public static final String REMOTE_ACCOUNT_LABEL = "CTSRemoteConnectionService";
+    public static final String SELF_MANAGED_ACCOUNT_LABEL = "CtsSelfManagedConnectionService";
 
     private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer ";
 
@@ -58,6 +63,8 @@
 
     private static final String COMMAND_REGISTER_SIM = "telecom register-sim-phone-account ";
 
+    private static final String COMMAND_WAIT_ON_HANDLERS = "telecom wait-on-handlers";
+
     public static final String MERGE_CALLER_NAME = "calls-merged";
     public static final String SWAP_CALLER_NAME = "calls-swapped";
     private static final String PRIMARY_USER_SN = "0";
@@ -100,6 +107,10 @@
                 + handle.getId() + " " + PRIMARY_USER_SN + " " + label + " " + address);
     }
 
+    public static void waitOnAllHandlers(Instrumentation instrumentation) throws Exception {
+        executeShellCommand(instrumentation, COMMAND_WAIT_ON_HANDLERS);
+    }
+
     /**
      * Executes the given shell command and returns the output in a string. Note that even
      * if we don't care about the output, we have to read the stream completely to make the
diff --git a/tests/tests/text/src/android/text/cts/EmojiTest.java b/tests/tests/text/src/android/text/cts/EmojiTest.java
index 2e5bbad..e841fb5 100644
--- a/tests/tests/text/src/android/text/cts/EmojiTest.java
+++ b/tests/tests/text/src/android/text/cts/EmojiTest.java
@@ -184,7 +184,7 @@
         }
     }
 
-    private class CaptureCanvas extends View {
+    private static class CaptureCanvas extends View {
 
         String mTestStr;
         Paint paint = new Paint();
@@ -216,7 +216,7 @@
 
     }
 
-    private class CaptureTextView extends TextView {
+    private static class CaptureTextView extends TextView {
 
         CaptureTextView(Context context) {
             super(context);
@@ -240,7 +240,7 @@
 
     }
 
-    private class CaptureEditText extends EditText {
+    private static class CaptureEditText extends EditText {
 
         CaptureEditText(Context context) {
             super(context);
diff --git a/tests/tests/text/src/android/text/cts/FontManagerTest.java b/tests/tests/text/src/android/text/cts/FontManagerTest.java
index 69ea522..1e3631a 100644
--- a/tests/tests/text/src/android/text/cts/FontManagerTest.java
+++ b/tests/tests/text/src/android/text/cts/FontManagerTest.java
@@ -15,64 +15,76 @@
  */
 package android.text.cts;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.FontConfig;
 import android.text.FontManager;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.FileDescriptor;
 import java.util.List;
 
 /**
  * Tests {@link FontManager}.
  */
-public class FontManagerTest extends AndroidTestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FontManagerTest {
 
     private FontManager mFontManager;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-        mFontManager = (FontManager) getContext().getSystemService(Context.FONT_SERVICE);
+        final Context targetContext = InstrumentationRegistry.getTargetContext();
+        mFontManager = (FontManager) targetContext.getSystemService(Context.FONT_SERVICE);
     }
 
-    @SmallTest
+    @Test
     public void testGetSystemFontsData() {
-        FontConfig config = mFontManager.getSystemFonts();
+        final FontConfig config = mFontManager.getSystemFonts();
 
         assertNotNull(config);
         assertTrue("There should at least be one font family", config.getFamilies().size() > 0);
         for (int i = 0; i < config.getFamilies().size(); ++i) {
-            FontConfig.Family family = config.getFamilies().get(i);
+            final FontConfig.Family family = config.getFamilies().get(i);
             assertTrue("Each font family should have at least one font",
                     family.getFonts().size() > 0);
             for (int j = 0; j < family.getFonts().size(); ++j) {
-                FontConfig.Font font = family.getFonts().get(j);
+                final FontConfig.Font font = family.getFonts().get(j);
                 assertNotNull("FontManager should provide a FileDescriptor for each system font",
                         font.getFd());
             }
         }
     }
 
-    @SmallTest
+    @Test
     public void testFileDescriptorsAreReadOnly() throws Exception {
-        FontConfig fc = mFontManager.getSystemFonts();
+        final FontConfig fc = mFontManager.getSystemFonts();
 
-        List<FontConfig.Family> families = fc.getFamilies();
+        final List<FontConfig.Family> families = fc.getFamilies();
         for (int i = 0; i < families.size(); ++i) {
-            List<FontConfig.Font> fonts = families.get(i).getFonts();
+            final List<FontConfig.Font> fonts = families.get(i).getFonts();
             for (int j = 0; j < fonts.size(); ++j) {
-                ParcelFileDescriptor pfd = fonts.get(j).getFd();
+                final ParcelFileDescriptor pfd = fonts.get(j).getFd();
                 assertNotNull(pfd);
-                FileDescriptor fd = pfd.getFileDescriptor();
+                final FileDescriptor fd = pfd.getFileDescriptor();
                 long size = Os.lseek(fd, 0, OsConstants.SEEK_END);
                 // Read only mapping should success.
-                long addr = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_SHARED, fd, 0);
+                final long addr = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_SHARED,
+                        fd, 0);
                 Os.munmap(addr, size);
 
                 // Mapping with PROT_WRITE should fail with EPERM.
diff --git a/tests/tests/text/src/android/text/cts/LayoutTest.java b/tests/tests/text/src/android/text/cts/LayoutTest.java
index d94fb23..6b6c4fc 100644
--- a/tests/tests/text/src/android/text/cts/LayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/LayoutTest.java
@@ -323,7 +323,7 @@
         assertTrue(widthLongest > widthLonger);
     }
 
-    private final class MockLayout extends Layout {
+    private static final class MockLayout extends Layout {
         public MockLayout(CharSequence text, TextPaint paint, int width,
                 Alignment align, float spacingmult, float spacingadd) {
             super(text, paint, width, align, spacingmult, spacingadd);
diff --git a/tests/tests/text/src/android/text/cts/MyanmarTest.java b/tests/tests/text/src/android/text/cts/MyanmarTest.java
index 5ded363..8a71adb 100644
--- a/tests/tests/text/src/android/text/cts/MyanmarTest.java
+++ b/tests/tests/text/src/android/text/cts/MyanmarTest.java
@@ -60,7 +60,7 @@
         }
     }
 
-    private class CaptureTextView extends TextView {
+    private static class CaptureTextView extends TextView {
 
         CaptureTextView(Context context) {
             super(context);
diff --git a/tests/tests/text/src/android/text/cts/SpannableStringBuilderSpanTest.java b/tests/tests/text/src/android/text/cts/SpannableStringBuilderSpanTest.java
index f693038..8ca7598 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringBuilderSpanTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringBuilderSpanTest.java
@@ -401,7 +401,7 @@
 
         private Spannable mSpannable;
 
-        private class AddedRemoved {
+        private static class AddedRemoved {
             Object span;
             int start;
             int end;
@@ -413,7 +413,7 @@
             }
         }
 
-        private class Changed {
+        private static class Changed {
             Object span;
             int oldStart;
             int oldEnd;
diff --git a/tests/tests/text/src/android/text/cts/SpannableStringTest.java b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
index 81dbf9b..229bea6 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
@@ -16,19 +16,31 @@
 
 package android.text.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
 import android.support.test.filters.SmallTest;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.NoCopySpan;
 import android.text.SpannableString;
 import android.text.Spanned;
 import android.text.style.LocaleSpan;
 import android.text.style.QuoteSpan;
 import android.text.style.UnderlineSpan;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.Locale;
 
-public class SpannableStringTest extends AndroidTestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SpannableStringTest {
 
-    @SmallTest
+    @Test
     public void testConstructor() {
         new SpannableString("test");
 
@@ -39,7 +51,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testValueOf() {
         String text = "test valueOf";
         SpannableString spannable = SpannableString.valueOf(text);
@@ -56,7 +68,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testSetSpan() {
         String text = "hello, world";
         SpannableString spannable = new SpannableString(text);
@@ -87,7 +99,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testRemoveSpan() {
         SpannableString spannable = new SpannableString("hello, world");
 
@@ -111,7 +123,7 @@
         assertEquals(0, spannable.getSpanFlags(underlineSpan));
     }
 
-    @SmallTest
+    @Test
     public void testSubSequence() {
         String text = "hello, world";
         SpannableString spannable = new SpannableString(text);
@@ -135,7 +147,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testSubsequence_copiesSpans() {
         SpannableString first = new SpannableString("t\nest data");
         QuoteSpan quoteSpan = new QuoteSpan();
@@ -166,7 +178,7 @@
     }
 
 
-    @SmallTest
+    @Test
     public void testCopyConstructor_copiesAllSpans() {
         SpannableString first = new SpannableString("t\nest data");
         first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
@@ -189,7 +201,24 @@
         }
     }
 
-    @SmallTest
+    @Test
+    public void testCopyConstructor_doesNotCopyNoCopySpans() {
+        final SpannableString first = new SpannableString("t\nest data");
+        first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
+        first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
+
+        final SpannableString copied = new SpannableString(first);
+        final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
+        assertNotNull(spans);
+        assertEquals(2, spans.length);
+
+        for (int i = 0; i < spans.length; i++) {
+            assertFalse(spans[i] instanceof NoCopySpan);
+        }
+    }
+
+    @Test
     public void testCopyGrowable() {
         SpannableString first = new SpannableString("t\nest data");
         final int N_SPANS = 127;
diff --git a/tests/tests/text/src/android/text/cts/SpannedStringTest.java b/tests/tests/text/src/android/text/cts/SpannedStringTest.java
index ccdb119..b20469e 100644
--- a/tests/tests/text/src/android/text/cts/SpannedStringTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannedStringTest.java
@@ -17,13 +17,20 @@
 package android.text.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.text.NoCopySpan;
+import android.text.SpannableString;
+import android.text.Spanned;
 import android.text.SpannedString;
+import android.text.style.QuoteSpan;
+import android.text.style.UnderlineSpan;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -81,4 +88,22 @@
         } catch (StringIndexOutOfBoundsException e) {
         }
     }
+
+    @Test
+    public void testCopyConstructor_doesNotCopyNoCopySpans() {
+        final SpannableString first = new SpannableString("t\nest data");
+        first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
+        first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
+
+        final SpannedString copied = new SpannedString(first);
+        final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
+        assertNotNull(spans);
+        assertEquals(2, spans.length);
+
+        for (int i = 0; i < spans.length; i++) {
+            assertFalse(spans[i] instanceof NoCopySpan);
+        }
+    }
+
 }
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index da51c74..d1e3ac3 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -76,8 +76,8 @@
     private StaticLayout mDefaultLayout;
     private TextPaint mDefaultPaint;
 
-    private class TestingTextPaint extends TextPaint {
-        // need to have a subclass to insure measurement happens in Java and not C++
+    private static class TestingTextPaint extends TextPaint {
+        // need to have a subclass to ensure measurement happens in Java and not C++
     }
 
     @Before
diff --git a/tests/tests/text/src/android/text/cts/TextUtilsTest.java b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
index 4d634fb..979dc15 100644
--- a/tests/tests/text/src/android/text/cts/TextUtilsTest.java
+++ b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
@@ -979,7 +979,7 @@
     /**
      * MockGetChars for test.
      */
-    private class MockGetChars implements GetChars {
+    private static class MockGetChars implements GetChars {
         private boolean mHasCalledGetChars;
         private GetCharsParams mGetCharsParams = new GetCharsParams();
 
@@ -1026,7 +1026,7 @@
     /**
      * MockCharSequence for test.
      */
-    private class MockCharSequence implements CharSequence {
+    private static class MockCharSequence implements CharSequence {
         private char mText[];
 
         public MockCharSequence(String text) {
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionListenerAdapterTest.java b/tests/tests/transition/src/android/transition/cts/TransitionListenerAdapterTest.java
new file mode 100644
index 0000000..1438788
--- /dev/null
+++ b/tests/tests/transition/src/android/transition/cts/TransitionListenerAdapterTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 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.transition.cts;
+
+import android.transition.TransitionListenerAdapter;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TransitionListenerAdapterTest {
+    /**
+     * TransitionListenerAdapter has a noop implementation of the TransitionListener interface.
+     * It should do nothing, including when nulls are passed to it.
+     * <p>
+     * Mostly this test pokes the implementation so that it is counted as tested. There isn't
+     * much to test here since it has no implementation.
+     */
+    @Test
+    public void testNullOk() {
+        TransitionListenerAdapter adapter = new MyAdapter();
+        adapter.onTransitionStart(null);
+        adapter.onTransitionEnd(null);
+        adapter.onTransitionCancel(null);
+        adapter.onTransitionPause(null);
+        adapter.onTransitionResume(null);
+    }
+
+    private static class MyAdapter extends TransitionListenerAdapter {
+    }
+}
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionTest.java b/tests/tests/transition/src/android/transition/cts/TransitionTest.java
index 506ea7b..b60f1ca 100644
--- a/tests/tests/transition/src/android/transition/cts/TransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/TransitionTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -443,7 +444,7 @@
         assertFalse(transition.animators.isEmpty());
         Animator animator = transition.animators.get(redSquare);
         Animator.AnimatorListener listener = transition.listeners.get(redSquare);
-        verify(listener, within(100)).onAnimationStart(any());
+        verify(listener, within(100)).onAnimationStart(any(), eq(false));
         assertSame(interpolator, animator.getInterpolator());
         endTransition();
     }
@@ -500,7 +501,7 @@
         Animator redSquareAnimator = transition.animators.get(redSquare);
         Animator greenSquareAnimator = transition.animators.get(greenSquare);
         Animator.AnimatorListener listener = transition.listeners.get(redSquare);
-        verify(listener, within(100)).onAnimationStart(any());
+        verify(listener, within(100)).onAnimationStart(any(), eq(false));
         assertEquals(0, redSquareAnimator.getStartDelay());
         assertEquals(diffTop, greenSquareAnimator.getStartDelay());
         endTransition();
@@ -532,7 +533,7 @@
         Animator animator = transition.animators.get(redSquare);
         assertFalse(animator.isRunning());
         Animator.AnimatorListener listener = transition.listeners.get(redSquare);
-        verify(listener, within(250)).onAnimationStart(any());
+        verify(listener, within(250)).onAnimationStart(any(), eq(false));
         endTransition();
     }
 
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
index 6cd4f4e..708477a 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
@@ -27,6 +27,7 @@
 import android.media.tv.TvContentRating;
 import android.media.tv.TvContract;
 import android.media.tv.TvContract.Channels;
+import android.media.tv.TvContract.Programs;
 import android.media.tv.TvContract.Programs.Genres;
 import android.media.tv.TvContract.RecordedPrograms;
 import android.net.Uri;
@@ -44,44 +45,44 @@
  */
 public class TvContractTest extends AndroidTestCase {
     private static final String[] CHANNELS_PROJECTION = {
-        TvContract.Channels._ID,
-        TvContract.Channels.COLUMN_INPUT_ID,
-        TvContract.Channels.COLUMN_TYPE,
-        TvContract.Channels.COLUMN_SERVICE_TYPE,
-        TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID,
-        TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID,
-        TvContract.Channels.COLUMN_SERVICE_ID,
-        TvContract.Channels.COLUMN_DISPLAY_NUMBER,
-        TvContract.Channels.COLUMN_DISPLAY_NAME,
-        TvContract.Channels.COLUMN_NETWORK_AFFILIATION,
-        TvContract.Channels.COLUMN_DESCRIPTION,
-        TvContract.Channels.COLUMN_VIDEO_FORMAT,
-        TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA,
-        TvContract.Channels.COLUMN_VERSION_NUMBER,
+        Channels._ID,
+        Channels.COLUMN_INPUT_ID,
+        Channels.COLUMN_TYPE,
+        Channels.COLUMN_SERVICE_TYPE,
+        Channels.COLUMN_ORIGINAL_NETWORK_ID,
+        Channels.COLUMN_TRANSPORT_STREAM_ID,
+        Channels.COLUMN_SERVICE_ID,
+        Channels.COLUMN_DISPLAY_NUMBER,
+        Channels.COLUMN_DISPLAY_NAME,
+        Channels.COLUMN_NETWORK_AFFILIATION,
+        Channels.COLUMN_DESCRIPTION,
+        Channels.COLUMN_VIDEO_FORMAT,
+        Channels.COLUMN_INTERNAL_PROVIDER_DATA,
+        Channels.COLUMN_VERSION_NUMBER,
     };
 
     private static final String[] PROGRAMS_PROJECTION = {
-        TvContract.Programs._ID,
-        TvContract.Programs.COLUMN_CHANNEL_ID,
-        TvContract.Programs.COLUMN_TITLE,
-        TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
-        TvContract.Programs.COLUMN_SEASON_TITLE,
-        TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
-        TvContract.Programs.COLUMN_EPISODE_TITLE,
-        TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
-        TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
-        TvContract.Programs.COLUMN_BROADCAST_GENRE,
-        TvContract.Programs.COLUMN_CANONICAL_GENRE,
-        TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
-        TvContract.Programs.COLUMN_LONG_DESCRIPTION,
-        TvContract.Programs.COLUMN_VIDEO_WIDTH,
-        TvContract.Programs.COLUMN_VIDEO_HEIGHT,
-        TvContract.Programs.COLUMN_AUDIO_LANGUAGE,
-        TvContract.Programs.COLUMN_CONTENT_RATING,
-        TvContract.Programs.COLUMN_POSTER_ART_URI,
-        TvContract.Programs.COLUMN_THUMBNAIL_URI,
-        TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA,
-        TvContract.Programs.COLUMN_VERSION_NUMBER,
+        Programs._ID,
+        Programs.COLUMN_CHANNEL_ID,
+        Programs.COLUMN_TITLE,
+        Programs.COLUMN_SEASON_DISPLAY_NUMBER,
+        Programs.COLUMN_SEASON_TITLE,
+        Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
+        Programs.COLUMN_EPISODE_TITLE,
+        Programs.COLUMN_START_TIME_UTC_MILLIS,
+        Programs.COLUMN_END_TIME_UTC_MILLIS,
+        Programs.COLUMN_BROADCAST_GENRE,
+        Programs.COLUMN_CANONICAL_GENRE,
+        Programs.COLUMN_SHORT_DESCRIPTION,
+        Programs.COLUMN_LONG_DESCRIPTION,
+        Programs.COLUMN_VIDEO_WIDTH,
+        Programs.COLUMN_VIDEO_HEIGHT,
+        Programs.COLUMN_AUDIO_LANGUAGE,
+        Programs.COLUMN_CONTENT_RATING,
+        Programs.COLUMN_POSTER_ART_URI,
+        Programs.COLUMN_THUMBNAIL_URI,
+        Programs.COLUMN_INTERNAL_PROVIDER_DATA,
+        Programs.COLUMN_VERSION_NUMBER,
     };
 
     private static long OPERATION_TIME = 1000l;
@@ -101,6 +102,7 @@
     private static final String WHITE_SPACES = " \r \n \t \f ";
 
     private static final String PARAM_CANONICAL_GENRE = "canonical_genre";
+    private static final String NON_EXISTING_COLUMN_NAME = "non_existing_column";
 
     private String mInputId;
     private ContentResolver mContentResolver;
@@ -127,7 +129,7 @@
         // Clean up, just in case we failed to delete the entry when a test failed.
         // The cotentUris are specific to this package, so this will delete only the
         // entries inserted by this package.
-        String[] projection = { TvContract.Channels._ID };
+        String[] projection = { Channels._ID };
         try (Cursor cursor = mContentResolver.query(mChannelsUri, projection, null, null, null)) {
             while (cursor != null && cursor.moveToNext()) {
                 long channelId = cursor.getLong(0);
@@ -142,92 +144,106 @@
 
     private static ContentValues createDummyChannelValues(String inputId, boolean preview) {
         ContentValues values = new ContentValues();
-        values.put(TvContract.Channels.COLUMN_INPUT_ID, inputId);
-        values.put(TvContract.Channels.COLUMN_TYPE,
-                preview ? TvContract.Channels.TYPE_PREVIEW : TvContract.Channels.TYPE_OTHER);
-        values.put(TvContract.Channels.COLUMN_SERVICE_TYPE,
-                TvContract.Channels.SERVICE_TYPE_AUDIO_VIDEO);
-        values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, "1");
-        values.put(TvContract.Channels.COLUMN_VIDEO_FORMAT, TvContract.Channels.VIDEO_FORMAT_480P);
+        values.put(Channels.COLUMN_INPUT_ID, inputId);
+        values.put(Channels.COLUMN_TYPE, preview ? Channels.TYPE_PREVIEW : Channels.TYPE_OTHER);
+        values.put(Channels.COLUMN_SERVICE_TYPE, Channels.SERVICE_TYPE_AUDIO_VIDEO);
+        values.put(Channels.COLUMN_DISPLAY_NUMBER, "1");
+        values.put(Channels.COLUMN_VIDEO_FORMAT, Channels.VIDEO_FORMAT_480P);
 
         return values;
     }
 
     private static ContentValues createDummyProgramValues(long channelId) {
         ContentValues values = new ContentValues();
-        values.put(TvContract.Programs.COLUMN_CHANNEL_ID, channelId);
-        values.put(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
-        values.put(TvContract.Programs.COLUMN_EPISODE_TITLE, "episode_title");
-        values.put(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
-        values.put(TvContract.Programs.COLUMN_SEASON_TITLE, "season_title");
-        values.put(TvContract.Programs.COLUMN_CANONICAL_GENRE, TvContract.Programs.Genres.encode(
-                TvContract.Programs.Genres.MOVIES, TvContract.Programs.Genres.DRAMA));
+        values.put(Programs.COLUMN_CHANNEL_ID, channelId);
+        values.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
+        values.put(Programs.COLUMN_EPISODE_TITLE, "episode_title");
+        values.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
+        values.put(Programs.COLUMN_SEASON_TITLE, "season_title");
+        values.put(Programs.COLUMN_CANONICAL_GENRE, Programs.Genres.encode(
+                Programs.Genres.MOVIES, Programs.Genres.DRAMA));
         TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
                 "US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
-        values.put(TvContract.Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
+        values.put(Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
 
         return values;
     }
 
     private static ContentValues createDummyPreviewProgramValues(long channelId) {
         ContentValues values = new ContentValues();
-        values.put(TvContract.Programs.COLUMN_CHANNEL_ID, channelId);
-        values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_ID, "ID-4321");
-        values.put(TvContract.Programs.COLUMN_PREVIEW_VIDEO_URI, "http://test.com/preview.mp4");
-        values.put(TvContract.Programs.COLUMN_PREVIEW_LAST_PLAYBACK_POSITION, 5000);
-        values.put(TvContract.Programs.COLUMN_PREVIEW_DURATION, 60000);
-        values.put(TvContract.Programs.COLUMN_PREVIEW_INTENT_URI,
-                "preview_app_link_intent");
-        values.put(TvContract.Programs.COLUMN_PREVIEW_WEIGHT, 100);
-        values.put(TvContract.Programs.COLUMN_TITLE, "program_title");
-        values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, "short_description");
-        values.put(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
-        values.put(TvContract.Programs.COLUMN_EPISODE_TITLE, "episode_title");
-        values.put(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
-        values.put(TvContract.Programs.COLUMN_SEASON_TITLE, "season_title");
-        values.put(TvContract.Programs.COLUMN_CANONICAL_GENRE, TvContract.Programs.Genres.encode(
-                TvContract.Programs.Genres.SPORTS, TvContract.Programs.Genres.DRAMA));
+        values.put(Programs.COLUMN_CHANNEL_ID, channelId);
+        values.put(Programs.COLUMN_INTERNAL_PROVIDER_ID, "ID-4321");
+        values.put(Programs.COLUMN_PREVIEW_VIDEO_URI, "http://test.com/preview.mp4");
+        values.put(Programs.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, 5000);
+        values.put(Programs.COLUMN_DURATION_MILLIS, 60000);
+        values.put(Programs.COLUMN_APP_LINK_INTENT_URI, "app_link_intent");
+        values.put(Programs.COLUMN_WEIGHT, 100);
+        values.put(Programs.COLUMN_TITLE, "program_title");
+        values.put(Programs.COLUMN_SHORT_DESCRIPTION, "short_description");
+        values.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
+        values.put(Programs.COLUMN_EPISODE_TITLE, "episode_title");
+        values.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
+        values.put(Programs.COLUMN_SEASON_TITLE, "season_title");
+        values.put(Programs.COLUMN_CANONICAL_GENRE, Programs.Genres.encode(
+                Programs.Genres.SPORTS, Programs.Genres.DRAMA));
         TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
                 "US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
-        values.put(TvContract.Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
+        values.put(Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
+        values.put(Programs.COLUMN_TYPE, Programs.TYPE_MOVIE);
+        values.put(Programs.COLUMN_WATCH_NEXT_TYPE, Programs.WATCH_NEXT_TYPE_CONTINUE);
+        values.put(Programs.COLUMN_POSTER_ART_URI, "http://foo.com/artwork.png");
+        values.put(Programs.COLUMN_POSTER_ART_ASPECT_RATIO, Programs.ASPECT_RATIO_2_3);
+        values.put(Programs.COLUMN_THUMBNAIL_URI, "http://foo.com/thumbnail.jpg");
+        values.put(Programs.COLUMN_THUMBNAIL_ASPECT_RATIO, Programs.ASPECT_RATIO_16_9);
+        values.put(Programs.COLUMN_LOGO_URI, "http://foo.com/logo.jpg");
+        values.put(Programs.COLUMN_AVAILABILITY, Programs.AVAILABILITY_AVAILABLE);
+        values.put(Programs.COLUMN_STARTING_PRICE, "10.99 USD");
+        values.put(Programs.COLUMN_OFFER_PRICE, "3.99 USD");
+        values.put(Programs.COLUMN_RELEASE_DATE, "1985");
+        values.put(Programs.COLUMN_ITEM_COUNT, 1);
+        values.put(Programs.COLUMN_LIVE, 0);
+        values.put(Programs.COLUMN_INTERACTION_TYPE, Programs.INTERACTION_TYPE_LIKES);
+        values.put(Programs.COLUMN_INTERACTION_COUNT, 4000);
+        values.put(Programs.COLUMN_AUTHOR, "author_name1");
+        values.put(Programs.COLUMN_REVIEW_RATING_STYLE, Programs.REVIEW_RATING_STYLE_STARS);
+        values.put(Programs.COLUMN_REVIEW_RATING, "4.5");
 
         return values;
     }
 
     private static ContentValues createDummyRecordedProgramValues(String inputId, long channelId) {
         ContentValues values = new ContentValues();
-        values.put(TvContract.RecordedPrograms.COLUMN_INPUT_ID, inputId);
-        values.put(TvContract.RecordedPrograms.COLUMN_CHANNEL_ID, channelId);
-        values.put(TvContract.RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER , "3B");
-        values.put(TvContract.RecordedPrograms.COLUMN_SEASON_TITLE, "season_title");
-        values.put(TvContract.RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER , "2A");
-        values.put(TvContract.RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title");
-        values.put(TvContract.RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, 1000);
-        values.put(TvContract.RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, 2000);
-        values.put(TvContract.RecordedPrograms.COLUMN_CANONICAL_GENRE,
-                TvContract.Programs.Genres.encode(
-                        TvContract.Programs.Genres.MOVIES, TvContract.Programs.Genres.DRAMA));
-        values.put(TvContract.RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description");
-        values.put(TvContract.RecordedPrograms.COLUMN_LONG_DESCRIPTION, "long_description");
-        values.put(TvContract.RecordedPrograms.COLUMN_VIDEO_WIDTH, 1920);
-        values.put(TvContract.RecordedPrograms.COLUMN_VIDEO_HEIGHT, 1080);
-        values.put(TvContract.RecordedPrograms.COLUMN_AUDIO_LANGUAGE, "en");
+        values.put(RecordedPrograms.COLUMN_INPUT_ID, inputId);
+        values.put(RecordedPrograms.COLUMN_CHANNEL_ID, channelId);
+        values.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER , "3B");
+        values.put(RecordedPrograms.COLUMN_SEASON_TITLE, "season_title");
+        values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER , "2A");
+        values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title");
+        values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, 1000);
+        values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, 2000);
+        values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE,
+                Programs.Genres.encode(Programs.Genres.MOVIES, Programs.Genres.DRAMA));
+        values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description");
+        values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, "long_description");
+        values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, 1920);
+        values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, 1080);
+        values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, "en");
         TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
                 "US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
-        values.put(TvContract.RecordedPrograms.COLUMN_CONTENT_RATING, rating.flattenToString());
-        values.put(TvContract.RecordedPrograms.COLUMN_POSTER_ART_URI, "http://foo.com/artwork.png");
-        values.put(TvContract.RecordedPrograms.COLUMN_THUMBNAIL_URI, "http://foo.com/thumbnail.jpg");
-        values.put(TvContract.RecordedPrograms.COLUMN_SEARCHABLE, 1);
-        values.put(TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI, "file:///sdcard/foo/");
-        values.put(TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, 1024 * 1024);
-        values.put(TvContract.RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, 60 * 60 * 1000);
-        values.put(TvContract.RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, 1454480880L);
-        values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+        values.put(RecordedPrograms.COLUMN_CONTENT_RATING, rating.flattenToString());
+        values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, "http://foo.com/artwork.png");
+        values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, "http://foo.com/thumbnail.jpg");
+        values.put(RecordedPrograms.COLUMN_SEARCHABLE, 1);
+        values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, "file:///sdcard/foo/");
+        values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, 1024 * 1024);
+        values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, 60 * 60 * 1000);
+        values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, 1454480880L);
+        values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
                 "internal_provider_data".getBytes());
-        values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 4);
-        values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 3);
-        values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 2);
-        values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 1);
+        values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 4);
+        values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 3);
+        values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 2);
+        values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 1);
 
         return values;
     }
@@ -274,25 +290,35 @@
             assertNotNull(cursor);
             assertEquals(cursor.getCount(), 1);
             assertTrue(cursor.moveToNext());
-            assertEquals(channelId, cursor.getLong(cursor.getColumnIndex(TvContract.Channels._ID)));
-            verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_INPUT_ID);
-            verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_TYPE);
-            verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_SERVICE_TYPE);
-            verifyIntegerColumn(cursor, expectedValues,
-                    TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID);
-            verifyIntegerColumn(cursor, expectedValues,
-                    TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID);
-            verifyIntegerColumn(cursor, expectedValues,
-                    TvContract.Channels.COLUMN_SERVICE_ID);
-            verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_DISPLAY_NUMBER);
-            verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_DISPLAY_NAME);
-            verifyStringColumn(cursor, expectedValues,
-                    TvContract.Channels.COLUMN_NETWORK_AFFILIATION);
-            verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_DESCRIPTION);
-            verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_VIDEO_FORMAT);
-            verifyBlobColumn(cursor, expectedValues,
-                    TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA);
-            verifyIntegerColumn(cursor, expectedValues, TvContract.Channels.COLUMN_VERSION_NUMBER);
+            assertEquals(channelId, cursor.getLong(cursor.getColumnIndex(Channels._ID)));
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_INPUT_ID);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_TYPE);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_SERVICE_TYPE);
+            verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_ORIGINAL_NETWORK_ID);
+            verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_TRANSPORT_STREAM_ID);
+            verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_SERVICE_ID);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_DISPLAY_NUMBER);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_DISPLAY_NAME);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_NETWORK_AFFILIATION);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_DESCRIPTION);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_VIDEO_FORMAT);
+            verifyBlobColumn(cursor, expectedValues, Channels.COLUMN_INTERNAL_PROVIDER_DATA);
+            verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_VERSION_NUMBER);
+        }
+    }
+
+    private void verifyNonExistingColumn(Uri channelUri, long channelId) {
+        String[] projection = {
+                Channels._ID,
+                NON_EXISTING_COLUMN_NAME
+        };
+        try (Cursor cursor = mContentResolver.query(channelUri, projection, null, null, null)) {
+            assertNotNull(cursor);
+            assertEquals(cursor.getCount(), 1);
+            assertTrue(cursor.moveToNext());
+            assertEquals(channelId, cursor.getLong(0));
+            assertNull(cursor.getString(1));
+            assertEquals(0, cursor.getInt(1));
         }
     }
 
@@ -309,9 +335,9 @@
         verifyChannel(channelUri, values, channelId);
 
         // Test: update
-        values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, "1-1");
-        values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, "One dash one");
-        values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
+        values.put(Channels.COLUMN_DISPLAY_NUMBER, "1-1");
+        values.put(Channels.COLUMN_DISPLAY_NAME, "One dash one");
+        values.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
 
         mContentResolver.update(channelUri, values, null, null);
         verifyChannel(channelUri, values, channelId);
@@ -324,64 +350,150 @@
         }
     }
 
+    public void testChannelsTableForIllegalAccess() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        ContentValues values = createDummyChannelValues(mInputId, false);
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+
+        final String COLUMN_BROWSABLE = "browsable";
+        final String COLUMN_SYSTEM_APPROVED = "system_approved";
+
+        // Test: insert
+        values.put(COLUMN_BROWSABLE, 1);
+        values.putNull(COLUMN_SYSTEM_APPROVED);
+        try {
+            mContentResolver.insert(mChannelsUri, values);
+            fail("'" + COLUMN_BROWSABLE + "' should not be accessible.");
+        } catch (Exception e) {
+            // Expected.
+        }
+        values.putNull(COLUMN_BROWSABLE);
+        values.put(COLUMN_SYSTEM_APPROVED, 1);
+        try {
+            mContentResolver.insert(mChannelsUri, values);
+            fail("'" + COLUMN_SYSTEM_APPROVED + "' should not be accessible.");
+        } catch (Exception e) {
+            // Expected.
+        }
+
+        // Test: update
+        values.put(COLUMN_BROWSABLE, 1);
+        values.putNull(COLUMN_SYSTEM_APPROVED);
+        try {
+            mContentResolver.update(channelUri, values, null, null);
+            fail("'" + COLUMN_BROWSABLE + "' should not be accessible.");
+        } catch (Exception e) {
+            // Expected.
+        }
+        values.putNull(COLUMN_BROWSABLE);
+        values.put(COLUMN_SYSTEM_APPROVED, 1);
+        try {
+            mContentResolver.update(channelUri, values, null, null);
+            fail("'" + COLUMN_SYSTEM_APPROVED + "' should not be accessible.");
+        } catch (Exception e) {
+            // Expected.
+        }
+
+        mContentResolver.delete(mChannelsUri, null, null);
+        try (Cursor cursor = mContentResolver.query(
+                mChannelsUri, CHANNELS_PROJECTION, null, null, null)) {
+            assertEquals(0, cursor.getCount());
+        }
+    }
+
+    public void testChannelsTableForNonExistingColumns() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        ContentValues values = createDummyChannelValues(mInputId, false);
+        values.put(NON_EXISTING_COLUMN_NAME, "dummy value");
+        Uri rowUri = mContentResolver.insert(mChannelsUri, values);
+        long channelId = ContentUris.parseId(rowUri);
+        Uri channelUri = TvContract.buildChannelUri(channelId);
+        verifyChannel(channelUri, values, channelId);
+        verifyNonExistingColumn(channelUri, channelId);
+
+        // Test: update
+        mContentResolver.update(channelUri, values, null, null);
+        verifyChannel(channelUri, values, channelId);
+        verifyNonExistingColumn(channelUri, channelId);
+
+        // Test: delete
+        mContentResolver.delete(mChannelsUri, null, null);
+        try (Cursor cursor = mContentResolver.query(
+                mChannelsUri, CHANNELS_PROJECTION, null, null, null)) {
+            assertEquals(0, cursor.getCount());
+        }
+    }
+
     private void verifyProgram(Uri programUri, ContentValues expectedValues, long programId) {
         try (Cursor cursor = mContentResolver.query(
                 programUri, null, null, null, null)) {
             assertNotNull(cursor);
             assertEquals(cursor.getCount(), 1);
             assertTrue(cursor.moveToNext());
-            assertEquals(programId, cursor.getLong(cursor.getColumnIndex(TvContract.Programs._ID)));
-            verifyLongColumn(cursor, expectedValues, TvContract.Programs.COLUMN_CHANNEL_ID);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_TITLE);
-            verifyStringColumn(cursor, expectedValues,
-                    TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_SEASON_TITLE);
-            verifyStringColumn(cursor, expectedValues,
-                    TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_EPISODE_TITLE);
-            verifyLongColumn(cursor, expectedValues,
-                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS);
-            verifyLongColumn(cursor, expectedValues,
-                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_BROADCAST_GENRE);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_CANONICAL_GENRE);
-            verifyStringColumn(cursor, expectedValues,
-                    TvContract.Programs.COLUMN_SHORT_DESCRIPTION);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_LONG_DESCRIPTION);
-            verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_VIDEO_WIDTH);
-            verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_VIDEO_HEIGHT);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_AUDIO_LANGUAGE);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_CONTENT_RATING);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_POSTER_ART_URI);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_THUMBNAIL_URI);
-            verifyBlobColumn(cursor, expectedValues,
-                    TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA);
-            verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_VERSION_NUMBER);
+            assertEquals(programId, cursor.getLong(cursor.getColumnIndex(Programs._ID)));
+            verifyLongColumn(cursor, expectedValues, Programs.COLUMN_CHANNEL_ID);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_TITLE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_SEASON_DISPLAY_NUMBER);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_SEASON_TITLE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_EPISODE_TITLE);
+            verifyLongColumn(cursor, expectedValues, Programs.COLUMN_START_TIME_UTC_MILLIS);
+            verifyLongColumn(cursor, expectedValues, Programs.COLUMN_END_TIME_UTC_MILLIS);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_BROADCAST_GENRE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_CANONICAL_GENRE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_SHORT_DESCRIPTION);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_LONG_DESCRIPTION);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VIDEO_WIDTH);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VIDEO_HEIGHT);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_AUDIO_LANGUAGE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_CONTENT_RATING);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_POSTER_ART_URI);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_THUMBNAIL_URI);
+            verifyBlobColumn(cursor, expectedValues, Programs.COLUMN_INTERNAL_PROVIDER_DATA);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VERSION_NUMBER);
 
-            verifyStringColumn(cursor, expectedValues,
-                    TvContract.Programs.COLUMN_INTERNAL_PROVIDER_ID);
-            verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_PREVIEW_VIDEO_URI);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_INTERNAL_PROVIDER_ID);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_PREVIEW_VIDEO_URI);
             verifyIntegerColumn(cursor, expectedValues,
-                    TvContract.Programs.COLUMN_PREVIEW_LAST_PLAYBACK_POSITION);
-            verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_PREVIEW_DURATION);
-            verifyStringColumn(cursor, expectedValues,
-                    TvContract.Programs.COLUMN_PREVIEW_INTENT_URI);
-            verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_PREVIEW_WEIGHT);
+                    Programs.COLUMN_LAST_PLAYBACK_POSITION_MILLIS);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_DURATION_MILLIS);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_APP_LINK_INTENT_URI);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_WEIGHT);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_TYPE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_WATCH_NEXT_TYPE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_POSTER_ART_ASPECT_RATIO);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_THUMBNAIL_ASPECT_RATIO);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_LOGO_URI);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_AVAILABILITY);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_STARTING_PRICE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_OFFER_PRICE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_RELEASE_DATE);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_ITEM_COUNT);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_LIVE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_INTERACTION_TYPE);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_INTERACTION_COUNT);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_AUTHOR);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_REVIEW_RATING_STYLE);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_REVIEW_RATING);
         }
     }
 
     private void verifyDeprecatedColumsInProgram(Uri programUri, ContentValues expectedValues) {
         final String[] DEPRECATED_COLUMNS_PROJECTION = {
-            TvContract.Programs.COLUMN_SEASON_NUMBER,
-            TvContract.Programs.COLUMN_EPISODE_NUMBER,
+            Programs.COLUMN_SEASON_NUMBER,
+            Programs.COLUMN_EPISODE_NUMBER,
         };
         try (Cursor cursor = mContentResolver.query(
                 programUri, DEPRECATED_COLUMNS_PROJECTION, null, null, null)) {
             assertNotNull(cursor);
             assertEquals(cursor.getCount(), 1);
             assertTrue(cursor.moveToNext());
-            verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_SEASON_NUMBER);
-            verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_EPISODE_NUMBER);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_SEASON_NUMBER);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_EPISODE_NUMBER);
         }
     }
 
@@ -434,9 +546,9 @@
         verifyProgram(programUri, values, programId);
 
         // Test: update
-        values.put(TvContract.Programs.COLUMN_TITLE, "new_program_title");
-        values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, "");
-        values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
+        values.put(Programs.COLUMN_TITLE, "new_program_title");
+        values.put(Programs.COLUMN_SHORT_DESCRIPTION, "");
+        values.put(Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
 
         mContentResolver.update(programUri, values, null, null);
         verifyProgram(programUri, values, programId);
@@ -461,9 +573,9 @@
         verifyProgram(programUri, values, programId);
 
         // Test: update
-        values.put(TvContract.Programs.COLUMN_EPISODE_TITLE, "Sample title");
-        values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, "Short description");
-        values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
+        values.put(Programs.COLUMN_EPISODE_TITLE, "Sample title");
+        values.put(Programs.COLUMN_SHORT_DESCRIPTION, "Short description");
+        values.put(Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
 
         mContentResolver.update(programUri, values, null, null);
         verifyProgram(programUri, values, programId);
@@ -482,14 +594,14 @@
         }
         // Test: insert
         ContentValues expected = createDummyProgramValues(channelId);
-        expected.put(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, "3");
-        expected.put(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "9");
+        expected.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, "3");
+        expected.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "9");
 
         ContentValues input = new ContentValues(expected);
-        input.remove(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER);
-        input.remove(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
-        input.put(TvContract.Programs.COLUMN_SEASON_NUMBER, 3);
-        input.put(TvContract.Programs.COLUMN_EPISODE_NUMBER, 9);
+        input.remove(Programs.COLUMN_SEASON_DISPLAY_NUMBER);
+        input.remove(Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
+        input.put(Programs.COLUMN_SEASON_NUMBER, 3);
+        input.put(Programs.COLUMN_EPISODE_NUMBER, 9);
 
         Uri rowUri = mContentResolver.insert(programsUri, input);
         long programId = ContentUris.parseId(rowUri);
@@ -498,10 +610,10 @@
         verifyDeprecatedColumsInProgram(programUri, input);
 
         // Test: update
-        expected.put(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, "4");
-        expected.put(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "10");
-        input.put(TvContract.Programs.COLUMN_SEASON_NUMBER, 4);
-        input.put(TvContract.Programs.COLUMN_EPISODE_NUMBER, 10);
+        expected.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, "4");
+        expected.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "10");
+        input.put(Programs.COLUMN_SEASON_NUMBER, 4);
+        input.put(Programs.COLUMN_EPISODE_NUMBER, 10);
 
         mContentResolver.update(programUri, input, null, null);
         verifyProgram(programUri, expected, programId);
@@ -571,8 +683,8 @@
         long channelId = ContentUris.parseId(channelUri);
         Uri programsUri = TvContract.buildProgramsUriForChannel(channelId);
         values = createDummyProgramValues(channelId);
-        values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, programStartMillis);
-        values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, programEndMillis);
+        values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, programStartMillis);
+        values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, programEndMillis);
         mContentResolver.insert(programsUri, values);
 
         // Overlap 1: starts early, ends early.
@@ -656,9 +768,9 @@
         verifyRecordedProgram(recordedProgramUri, values, recordedProgramId);
 
         // Test: update
-        values.put(TvContract.RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title1");
-        values.put(TvContract.RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description1");
-        values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+        values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title1");
+        values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description1");
+        values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
                 "internal_provider_data1".getBytes());
 
         mContentResolver.update(recordedProgramUri, values, null, null);
@@ -680,7 +792,7 @@
         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
         long channelId = ContentUris.parseId(channelUri);
 
-        verifyRecordedProgramsTable(TvContract.RecordedPrograms.CONTENT_URI, channelId);
+        verifyRecordedProgramsTable(RecordedPrograms.CONTENT_URI, channelId);
     }
 
     private void verifyQueryWithSortOrder(Uri uri, final String[] projection,
@@ -725,68 +837,60 @@
         if (!Utils.hasTvInputFramework(getContext())) {
             return;
         }
-        final String[] projection = { TvContract.Channels._ID };
-        verifyQueryWithSortOrder(TvContract.Channels.CONTENT_URI, projection,
-                TvContract.Channels._ID + " ASC");
+        final String[] projection = { Channels._ID };
+        verifyQueryWithSortOrder(Channels.CONTENT_URI, projection, Channels._ID + " ASC");
     }
 
     public void testAllEpgPermissionBlocksSelectionOnQuery_Channels() throws Exception {
         if (!Utils.hasTvInputFramework(getContext())) {
             return;
         }
-        final String[] projection = { TvContract.Channels._ID };
-        verifyQueryWithSelection(TvContract.Channels.CONTENT_URI, projection,
-                TvContract.Channels._ID + ">0");
+        final String[] projection = { Channels._ID };
+        verifyQueryWithSelection(Channels.CONTENT_URI, projection, Channels._ID + ">0");
     }
 
     public void testAllEpgPermissionBlocksSelectionOnUpdate_Channels() throws Exception {
         if (!Utils.hasTvInputFramework(getContext())) {
             return;
         }
-        verifyUpdateWithSelection(TvContract.Channels.CONTENT_URI,
-                TvContract.Channels._ID + ">0");
+        verifyUpdateWithSelection(Channels.CONTENT_URI, Channels._ID + ">0");
     }
 
     public void testAllEpgPermissionBlocksSelectionOnDelete_Channels() throws Exception {
         if (!Utils.hasTvInputFramework(getContext())) {
             return;
         }
-        verifyDeleteWithSelection(TvContract.Channels.CONTENT_URI,
-                TvContract.Channels._ID + ">0");
+        verifyDeleteWithSelection(Channels.CONTENT_URI, Channels._ID + ">0");
     }
 
     public void testAllEpgPermissionBlocksSortOrderOnQuery_Programs() throws Exception {
         if (!Utils.hasTvInputFramework(getContext())) {
             return;
         }
-        final String[] projection = { TvContract.Programs._ID };
-        verifyQueryWithSortOrder(TvContract.Programs.CONTENT_URI, projection,
-                TvContract.Programs._ID + " ASC");
+        final String[] projection = { Programs._ID };
+        verifyQueryWithSortOrder(Programs.CONTENT_URI, projection, Programs._ID + " ASC");
     }
 
     public void testAllEpgPermissionBlocksSelectionOnQuery_Programs() throws Exception {
         if (!Utils.hasTvInputFramework(getContext())) {
             return;
         }
-        final String[] projection = { TvContract.Channels._ID };
-        verifyQueryWithSelection(TvContract.Programs.CONTENT_URI, projection,
-                TvContract.Programs._ID + ">0");
+        final String[] projection = { Channels._ID };
+        verifyQueryWithSelection(Programs.CONTENT_URI, projection, Programs._ID + ">0");
     }
 
     public void testAllEpgPermissionBlocksSelectionOnUpdate_Programs() throws Exception {
         if (!Utils.hasTvInputFramework(getContext())) {
             return;
         }
-        verifyUpdateWithSelection(TvContract.Programs.CONTENT_URI,
-                TvContract.Programs._ID + ">0");
+        verifyUpdateWithSelection(Programs.CONTENT_URI, Programs._ID + ">0");
     }
 
     public void testAllEpgPermissionBlocksSelectionOnDelete_Programs() throws Exception {
         if (!Utils.hasTvInputFramework(getContext())) {
             return;
         }
-        verifyDeleteWithSelection(TvContract.Programs.CONTENT_URI,
-                TvContract.Programs._ID + ">0");
+        verifyDeleteWithSelection(Programs.CONTENT_URI, Programs._ID + ">0");
     }
 
     public void testDefaultValues() throws Exception {
@@ -794,17 +898,16 @@
             return;
         }
         ContentValues values = new ContentValues();
-        values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId);
+        values.put(Channels.COLUMN_INPUT_ID, mInputId);
         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
         assertNotNull(channelUri);
         try (Cursor cursor = mContentResolver.query(
                 channelUri, CHANNELS_PROJECTION, null, null, null)) {
             cursor.moveToNext();
-            assertEquals(TvContract.Channels.TYPE_OTHER,
-                    cursor.getString(cursor.getColumnIndex(TvContract.Channels.COLUMN_TYPE)));
-            assertEquals(TvContract.Channels.SERVICE_TYPE_AUDIO_VIDEO,
-                    cursor.getString(cursor.getColumnIndex(
-                            TvContract.Channels.COLUMN_SERVICE_TYPE)));
+            assertEquals(Channels.TYPE_OTHER,
+                    cursor.getString(cursor.getColumnIndex(Channels.COLUMN_TYPE)));
+            assertEquals(Channels.SERVICE_TYPE_AUDIO_VIDEO,
+                    cursor.getString(cursor.getColumnIndex(Channels.COLUMN_SERVICE_TYPE)));
         }
         values.clear();
     }
@@ -971,11 +1074,11 @@
         long channelId = ContentUris.parseId(channelUri);
         long curTime = System.currentTimeMillis();
         values = new ContentValues();
-        values.put(TvContract.Programs.COLUMN_CHANNEL_ID, channelId);
-        values.put(TvContract.Programs.COLUMN_BROADCAST_GENRE, Genres.encode(broadcastGenre));
-        values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, curTime - 60000);
-        values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, curTime + 60000);
-        Uri programUri = mContentResolver.insert(TvContract.Programs.CONTENT_URI, values);
+        values.put(Programs.COLUMN_CHANNEL_ID, channelId);
+        values.put(Programs.COLUMN_BROADCAST_GENRE, Genres.encode(broadcastGenre));
+        values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, curTime - 60000);
+        values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, curTime + 60000);
+        Uri programUri = mContentResolver.insert(Programs.CONTENT_URI, values);
         assertNotNull(programUri);
         return programUri;
     }
@@ -996,8 +1099,8 @@
         }
         String[] broadcastGenre = new String[] {"Animation", "Classic, opera"};
         insertProgramWithBroadcastGenre(broadcastGenre);
-        try (Cursor c = mContentResolver.query(TvContract.Programs.CONTENT_URI,
-                new String[] {TvContract.Programs.COLUMN_BROADCAST_GENRE}, null, null, null)) {
+        try (Cursor c = mContentResolver.query(Programs.CONTENT_URI,
+                new String[] {Programs.COLUMN_BROADCAST_GENRE}, null, null, null)) {
             assertNotNull(c);
             assertEquals(1, c.getCount());
             c.moveToNext();
diff --git a/tests/tests/uirendering/assets/linear-rgba16f.png b/tests/tests/uirendering/assets/linear-rgba16f.png
new file mode 100644
index 0000000..bad6a65
--- /dev/null
+++ b/tests/tests/uirendering/assets/linear-rgba16f.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/prophoto-rgba16f.png b/tests/tests/uirendering/assets/prophoto-rgba16f.png
new file mode 100644
index 0000000..5f855f2
--- /dev/null
+++ b/tests/tests/uirendering/assets/prophoto-rgba16f.png
Binary files differ
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java
index a954294..77ff8f2 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java
@@ -123,7 +123,8 @@
             canvas.setDrawFilter(null);
         };
         createTest()
-                .addCanvasClient(canvasClient)
+                // Picture does not support PaintFlagsDrawFilter
+                .addCanvasClientWithoutUsingPicture(canvasClient)
                 .runWithVerifier(getVerifierForTest(filterEnum, scaleUp));
     }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
index a7587ba..444af9f 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
@@ -202,13 +202,17 @@
                 new Rect(10, 10, 80, 80));
 
         createTest()
-                .addCanvasClient((canvas, width, height) -> {
+                // The border of the square is somehow blurred in HWUI OpenGL hardware mode with
+                // picture recording/playback. Maybe this is related to bug:31456967
+                // Hence disable picture mode for now.
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
                     canvas.drawColor(Color.WHITE);
                     Paint p = new Paint();
                     p.setColor(Color.BLUE);
                     canvas.drawRect(10, 10, 80, 80, p);
                 })
-                .addCanvasClient((canvas, width, height) -> ninePatchDrawable.draw(canvas))
+                .addCanvasClientWithoutUsingPicture(
+                        (canvas, width, height) -> ninePatchDrawable.draw(canvas))
                 .addLayout(R.layout.blue_padded_square, null)
                 .runWithVerifier(verifier);
     }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java
index 37713de..e808296 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java
@@ -54,6 +54,6 @@
                         // This means the source color (0x00ffffff) was
                         // properly pre-multiplied
                         0xffff0000
-                }, 10)); // Tolerance set to account for dithering and interpolation
+                }, 20)); // Tolerance set to account for dithering and interpolation
     }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
index 988480e..03a040f 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
@@ -65,7 +65,7 @@
 
     @Test
     public void testDecodeResource() {
-        createTest().addCanvasClient((canvas, width, height) -> {
+        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
             canvas.drawBitmap(hardwareBitmap, 0, 0, new Paint());
@@ -77,7 +77,7 @@
     public void testBitmapRegionDecode() throws IOException {
         InputStream inputStream = mRes.openRawResource(R.drawable.robot);
         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(inputStream, false);
-        createTest().addCanvasClient((canvas, width, height) -> {
+        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             Bitmap hardwareBitmap = decoder.decodeRegion(new Rect(10, 15, 34, 39),
                     HARDWARE_OPTIONS);
             canvas.drawBitmap(hardwareBitmap, 0, 0, new Paint());
@@ -119,11 +119,11 @@
 
     @Test
     public void testSetDensity() {
-        createTest().addCanvasClient((canvas, width, height) -> {
+        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot);
             bitmap.setDensity(DisplayMetrics.DENSITY_LOW);
             canvas.drawBitmap(bitmap, 0, 0, null);
-        }, true).addCanvasClient((canvas, width, height) -> {
+        }, true).addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
             hardwareBitmap.setDensity(DisplayMetrics.DENSITY_LOW);
@@ -133,7 +133,7 @@
 
     @Test
     public void testNinePatch() {
-        createTest().addCanvasClient((canvas, width, height) -> {
+        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             InputStream is = mRes.openRawResource(R.drawable.blue_padded_square);
             NinePatchDrawable ninePatch = (NinePatchDrawable) Drawable.createFromResourceStream(
                     mRes, null, is, null, HARDWARE_OPTIONS);
@@ -153,7 +153,7 @@
 
     @Test
     public void testCreateScaledBitmap() {
-        createTest().addCanvasClient((canvas, width, height) -> {
+        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
             Bitmap scaled = Bitmap.createScaledBitmap(hardwareBitmap, 24, 24, false);
@@ -165,7 +165,7 @@
 
     @Test
     public void testCreateSubsetBitmap() {
-        createTest().addCanvasClient((canvas, width, height) -> {
+        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
             Matrix matrix = new Matrix();
@@ -179,7 +179,7 @@
 
     @Test
     public void testCreateTransformedBitmap() {
-        createTest().addCanvasClient((canvas, width, height) -> {
+        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
             Matrix matrix = new Matrix();
@@ -198,10 +198,10 @@
         Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(), id, options);
         assertEquals(from, bitmap.getConfig());
 
-        createTest().addCanvasClient((canvas, width, height) -> {
+        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             canvas.drawColor(Color.CYAN);
             canvas.drawBitmap(bitmap, 0, 0, null);
-        }, true).addCanvasClient((canvas, width, height) -> {
+        }, true).addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
             canvas.drawColor(Color.CYAN);
             Bitmap copy = bitmap.copy(to, false);
             assertNotNull(copy);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
index 6f86ee7..f952bc23 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
@@ -63,7 +63,11 @@
             }
         };
         createTest()
-                .addCanvasClient(canvasClient)
+                // Because of the inverseComparer, we can't use Picture because
+                // software w/ picture = software w/o picture (same for hardware).
+                // (The inverseComparer assumes that there are only two render paths are they
+                // are different.)
+                .addCanvasClientWithoutUsingPicture(canvasClient)
                 .runWithComparer(inverseComparer);
     }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index 2e7d88f..4fc8bc8 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -375,8 +375,8 @@
     @Test
     public void testSaveLayerClippedWithAlpha() {
         // verify that renderer can draw nested clipped layers with different alpha
-        createTest()
-            .addCanvasClient((canvas, width, height) -> {
+        createTest() // picture mode is disable due to bug:34871089
+            .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
                 Paint redPaint = new Paint();
                 redPaint.setColor(0xffff0000);
                 canvas.saveLayerAlpha(40, 5, 80, 70, 0x7f, Canvas.CLIP_TO_LAYER_SAVE_FLAG);
@@ -391,8 +391,8 @@
     @Test
     public void testSaveLayerUnclippedWithAlpha() {
         // verify that renderer can draw nested unclipped layers with different alpha
-        createTest()
-            .addCanvasClient((canvas, width, height) -> {
+        createTest() // picture mode is disable due to bug:34871089
+            .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
                 Paint redPaint = new Paint();
                 redPaint.setColor(0xffff0000);
                 canvas.saveLayerAlpha(40, 5, 80, 70, 0x7f, 0);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Rgba16fTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Rgba16fTests.java
new file mode 100644
index 0000000..3faeb4a4
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Rgba16fTests.java
@@ -0,0 +1,100 @@
+/*
+ * 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.uirendering.cts.testclasses;
+
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Shader;
+import android.support.test.filters.MediumTest;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+@MediumTest
+public class Rgba16fTests extends ActivityTestBase {
+    @Test
+    public void testTransferFunctions() {
+        createTest()
+                .addCanvasClient("RGBA16F_TransferFunctions", (canvas, width, height) -> {
+                    AssetManager assets = getActivity().getResources().getAssets();
+                    try (InputStream in = assets.open("linear-rgba16f.png")) {
+                        Bitmap bitmap = BitmapFactory.decodeStream(in);
+                        canvas.scale(
+                                width / (float) bitmap.getWidth(),
+                                height / (float) bitmap.getHeight());
+                        canvas.drawBitmap(bitmap, 0, 0, null);
+                    } catch (IOException e) {
+                        throw new RuntimeException("Test failed: ", e);
+                    }
+                }, true)
+                .runWithVerifier(new SamplePointVerifier(
+                        new Point[] { new Point(0, 0) },
+                        new int[] { 0xffbbbbbb }
+                ));
+    }
+
+    @Test
+    public void testTransferFunctionsShader() {
+        createTest()
+                .addCanvasClient("RGBA16F_TransferFunctions_Shader", (canvas, width, height) -> {
+                    AssetManager assets = getActivity().getResources().getAssets();
+                    try (InputStream in = assets.open("linear-rgba16f.png")) {
+                        Bitmap bitmap = BitmapFactory.decodeStream(in);
+                        Paint p = new Paint();
+                        p.setShader(new BitmapShader(bitmap,
+                                Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+                        canvas.drawRect(0.0f, 0.0f, width, height, p);
+                    } catch (IOException e) {
+                        throw new RuntimeException("Test failed: ", e);
+                    }
+                }, true)
+                .runWithVerifier(new SamplePointVerifier(
+                        new Point[] { new Point(0, 0) },
+                        new int[] { 0xffbbbbbb }
+                ));
+    }
+
+    @Test
+    public void testMirroredTransferFunctions() {
+        createTest()
+                .addCanvasClient("RGBA16F_TransferFunctions_Mirror", (canvas, width, height) -> {
+                    AssetManager assets = getActivity().getResources().getAssets();
+                    // Pure blue in ProPhoto RGB will yield negative R and G values in scRGB,
+                    // as well as a value > 1.0 for B
+                    try (InputStream in = assets.open("prophoto-rgba16f.png")) {
+                        Bitmap bitmap = BitmapFactory.decodeStream(in);
+                        canvas.scale(
+                                width / (float) bitmap.getWidth(),
+                                height / (float) bitmap.getHeight());
+                        canvas.drawBitmap(bitmap, 0, 0, null);
+                    } catch (IOException e) {
+                        throw new RuntimeException("Test failed: ", e);
+                    }
+                }, true)
+                .runWithVerifier(new SamplePointVerifier(
+                        new Point[] { new Point(0, 0) },
+                        new int[] { 0xff0000ff }
+                ));
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java
index 856fc17..16df48a 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java
@@ -118,8 +118,9 @@
         // Create the test cases with each combination
         do {
             int arrIndex = Math.min(index, bitmapComparers.length - 1);
-            createTest()
-                    .addCanvasClient(modifierAccessor.getDebugString(), canvasClient)
+            createTest() // picture mode is disable due to bug:34871089
+                    .addCanvasClientWithoutUsingPicture(
+                            modifierAccessor.getDebugString(), canvasClient)
                     .runWithComparer(bitmapComparers[arrIndex]);
             index++;
         } while (modifierAccessor.step());
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
index e340899..ca5ee18 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
@@ -138,7 +138,7 @@
     protected Point runRenderSpec(TestCase testCase) {
         Point testOffset = getActivity().enqueueRenderSpecAndWait(
                 testCase.layoutID, testCase.canvasClient,
-                testCase.viewInitializer, testCase.useHardware);
+                testCase.viewInitializer, testCase.useHardware, testCase.usePicture);
         testCase.wasTestRan = true;
         if (testCase.readyFence != null) {
             try {
@@ -298,7 +298,28 @@
 
         public TestCaseBuilder addCanvasClient(String debugString,
                     CanvasClient canvasClient, boolean useHardware) {
-            mTestCases.add(new TestCase(canvasClient, debugString, useHardware));
+            return addCanvasClientInternal(debugString, canvasClient, useHardware, false)
+                    .addCanvasClientInternal(debugString, canvasClient, useHardware, true);
+        }
+
+        public TestCaseBuilder addCanvasClientWithoutUsingPicture(CanvasClient canvasClient) {
+            return addCanvasClientWithoutUsingPicture(null, canvasClient);
+        }
+
+        public TestCaseBuilder addCanvasClientWithoutUsingPicture(String debugString,
+                CanvasClient canvasClient) {
+            return addCanvasClientInternal(debugString, canvasClient, false, false)
+                    .addCanvasClientInternal(debugString, canvasClient, true, false);
+        }
+
+        public TestCaseBuilder addCanvasClientWithoutUsingPicture(CanvasClient canvasClient,
+                boolean useHardware) {
+            return addCanvasClientInternal(null, canvasClient, useHardware, false);
+        }
+
+        private TestCaseBuilder addCanvasClientInternal(String debugString,
+                CanvasClient canvasClient, boolean useHardware, boolean usePicture) {
+            mTestCases.add(new TestCase(canvasClient, debugString, useHardware, usePicture));
             return this;
         }
 
@@ -320,6 +341,7 @@
         public String canvasClientDebugString;
 
         public boolean useHardware;
+        public boolean usePicture = false;
         public boolean wasTestRan = false;
 
         public TestCase(int layoutId, ViewInitializer viewInitializer, boolean useHardware) {
@@ -328,10 +350,12 @@
             this.useHardware = useHardware;
         }
 
-        public TestCase(CanvasClient client, String debugString, boolean useHardware) {
+        public TestCase(CanvasClient client, String debugString, boolean useHardware,
+                boolean usePicture) {
             this.canvasClient = client;
             this.canvasClientDebugString = debugString;
             this.useHardware = useHardware;
+            this.usePicture = usePicture;
         }
 
         public String getDebugString() {
@@ -347,7 +371,8 @@
                 debug += "Layout resource : " +
                         getActivity().getResources().getResourceName(layoutID);
             }
-            debug += "\nTest ran in " + (useHardware ? "hardware" : "software") + "\n";
+            debug += "\nTest ran in " + (useHardware ? "hardware" : "software") +
+                    (usePicture ? " with picture" : " without picture") + "\n";
             return debug;
         }
     }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
index 81183e5..8012c82 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
@@ -19,6 +19,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Picture;
 import android.util.AttributeSet;
 import android.view.View;
 
@@ -26,6 +27,7 @@
  * A simple View that uses a CanvasClient to draw its contents
  */
 public class CanvasClientView extends View {
+    private boolean mUsePicture = false;
     private CanvasClient mCanvasClient;
 
     public CanvasClientView(Context context) {
@@ -40,6 +42,10 @@
         super(context, attrs, defStyleAttr);
     }
 
+    public void setUsePicture(boolean usePicture) {
+        mUsePicture = usePicture;
+    }
+
     public void setCanvasClient(CanvasClient canvasClient) {
         mCanvasClient = canvasClient;
     }
@@ -57,7 +63,17 @@
 
         int saveCount = canvas.save();
         canvas.clipRect(0, 0, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
-        mCanvasClient.draw(canvas, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+        if (mUsePicture) {
+            Picture picture = new Picture();
+            Canvas pictureCanvas = picture.beginRecording(ActivityTestBase.TEST_WIDTH,
+                    ActivityTestBase.TEST_HEIGHT);
+            mCanvasClient.draw(pictureCanvas, ActivityTestBase.TEST_WIDTH,
+                    ActivityTestBase.TEST_HEIGHT);
+            picture.endRecording();
+            picture.draw(canvas);
+        } else {
+            mCanvasClient.draw(canvas, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+        }
         canvas.restoreToCount(saveCount);
     }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
index 2c5a58e..13698c9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
@@ -54,11 +54,12 @@
     }
 
     public Point enqueueRenderSpecAndWait(int layoutId, CanvasClient canvasClient,
-            @Nullable ViewInitializer viewInitializer, boolean useHardware) {
+            @Nullable ViewInitializer viewInitializer, boolean useHardware, boolean usePicture) {
         ((RenderSpecHandler) mHandler).setViewInitializer(viewInitializer);
         int arg2 = (useHardware ? View.LAYER_TYPE_NONE : View.LAYER_TYPE_SOFTWARE);
         if (canvasClient != null) {
-            mHandler.obtainMessage(RenderSpecHandler.CANVAS_MSG, 0, arg2, canvasClient).sendToTarget();
+            mHandler.obtainMessage(RenderSpecHandler.CANVAS_MSG, usePicture ? 1 : 0,
+                    arg2, canvasClient).sendToTarget();
         } else {
             mHandler.obtainMessage(RenderSpecHandler.LAYOUT_MSG, layoutId, arg2).sendToTarget();
         }
@@ -126,6 +127,9 @@
                     stub.setLayoutResource(R.layout.test_content_canvasclientview);
                     mView = stub.inflate();
                     ((CanvasClientView) mView).setCanvasClient((CanvasClient) (message.obj));
+                    if (message.arg1 != 0) {
+                        ((CanvasClientView) mView).setUsePicture(true);
+                    }
                 } break;
             }
 
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 0c6f302..25ebdca 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -161,6 +161,9 @@
                 android:resource="@xml/merge" />
         </activity>
 
+        <activity android:name="android.view.cts.MenuTestActivity"
+                  android:label="MenuTestActivity" />
+
         <activity android:name="android.view.cts.ActionModeCtsActivity"
             android:label="ActionModeCtsActivity">
         </activity>
diff --git a/tests/tests/view/res/menu/shortcut_modifiers.xml b/tests/tests/view/res/menu/shortcut_modifiers.xml
new file mode 100644
index 0000000..4204940
--- /dev/null
+++ b/tests/tests/view/res/menu/shortcut_modifiers.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@+id/no_modifiers"
+        android:onClick="handleMenuItem"
+        android:alphabeticShortcut="a" />
+
+    <item android:id="@+id/default_modifiers"
+        android:alphabeticShortcut="b"
+        android:onClick="handleMenuItem"
+        android:alphabeticModifiers="CTRL" />
+
+    <item android:id="@+id/single_modifier"
+        android:alphabeticShortcut="c"
+        android:onClick="handleMenuItem"
+        android:alphabeticModifiers="SHIFT" />
+
+    <item android:id="@+id/multiple_modifiers"
+        android:alphabeticShortcut="d"
+        android:onClick="handleMenuItem"
+        android:alphabeticModifiers="CTRL|SHIFT" />
+
+</menu>
diff --git a/tests/tests/view/res/menu/visible_shortcut.xml b/tests/tests/view/res/menu/visible_shortcut.xml
index 4d6f362..57ceda1 100644
--- a/tests/tests/view/res/menu/visible_shortcut.xml
+++ b/tests/tests/view/res/menu/visible_shortcut.xml
@@ -34,4 +34,19 @@
 
     </group>
 
+    <item android:id="@+id/no_modifiers"
+          android:alphabeticShortcut="d" />
+
+    <item android:id="@+id/default_modifiers"
+          android:alphabeticShortcut="e"
+          android:alphabeticModifiers="CTRL" />
+
+    <item android:id="@+id/single_modifier"
+          android:alphabeticShortcut="f"
+          android:alphabeticModifiers="SHIFT" />
+
+    <item android:id="@+id/multiple_modifiers"
+          android:alphabeticShortcut="g"
+          android:alphabeticModifiers="CTRL|SHIFT" />
+
 </menu>
diff --git a/tests/tests/view/src/android/view/cts/MenuInflaterTest.java b/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
index bc9933f..2bb0278 100644
--- a/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
+++ b/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
@@ -31,6 +31,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -112,6 +113,30 @@
 
     @UiThreadTest
     @Test
+    public void testInflateShortcutModifiersFromXml() {
+        mMenuInflater.inflate(R.menu.visible_shortcut, mMenu);
+        MenuItem mMenuItem;
+
+        mMenuItem = mMenu.findItem(R.id.no_modifiers);
+        assertEquals('d', mMenuItem.getAlphabeticShortcut());
+        assertEquals(KeyEvent.META_CTRL_ON, mMenuItem.getAlphabeticModifiers());
+
+        mMenuItem = mMenu.findItem(R.id.default_modifiers);
+        assertEquals('e', mMenuItem.getAlphabeticShortcut());
+        assertEquals(KeyEvent.META_CTRL_ON, mMenuItem.getAlphabeticModifiers());
+
+        mMenuItem = mMenu.findItem(R.id.single_modifier);
+        assertEquals('f', mMenuItem.getAlphabeticShortcut());
+        assertEquals(KeyEvent.META_SHIFT_ON, mMenuItem.getAlphabeticModifiers());
+
+        mMenuItem = mMenu.findItem(R.id.multiple_modifiers);
+        assertEquals('g', mMenuItem.getAlphabeticShortcut());
+        assertEquals(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON,
+                mMenuItem.getAlphabeticModifiers());
+    }
+
+    @UiThreadTest
+    @Test
     public void testInflateDrawableFromXml() {
         // the titles and icons
         mMenuInflater.inflate(R.menu.title_icon, mMenu);
diff --git a/tests/tests/view/src/android/view/cts/MenuTest.java b/tests/tests/view/src/android/view/cts/MenuTest.java
new file mode 100644
index 0000000..d01cd8b
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/MenuTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.widget.PopupMenu;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link MenuInflater}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MenuTest {
+    private MenuTestActivity mActivity;
+    private MenuInflater mMenuInflater;
+    private Menu mMenu;
+
+    @Rule
+    public ActivityTestRule<MenuTestActivity> mActivityRule =
+            new ActivityTestRule<>(MenuTestActivity.class);
+
+    @Before
+    public void setup() {
+        mActivity = (MenuTestActivity) mActivityRule.getActivity();
+        mMenuInflater = mActivity.getMenuInflater();
+        mMenu = new PopupMenu(mActivity, null).getMenu();
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPerformShortcut() {
+        mMenuInflater.inflate(R.menu.shortcut_modifiers, mMenu);
+        mMenu.setQwertyMode(true);
+        final long downTime = SystemClock.uptimeMillis();
+        int keyCodeToSend, metaState;
+        KeyEvent keyEventToSend;
+
+        // Test shortcut trigger in case of no modifier
+        keyCodeToSend = KeyEvent.KEYCODE_A;
+        metaState = KeyEvent.META_CTRL_ON;
+        keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+                keyCodeToSend, 0, metaState);
+        assertTrue(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+        assertEquals(mActivity.getMenuItemIdTracker(),
+                mMenu.findItem(R.id.no_modifiers).getItemId());
+
+        // Test shortcut trigger in case of default modifier
+        keyCodeToSend = KeyEvent.KEYCODE_B;
+        metaState = KeyEvent.META_CTRL_ON;
+        keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+                keyCodeToSend, 0, metaState);
+        assertTrue(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+        assertEquals(mActivity.getMenuItemIdTracker(),
+                mMenu.findItem(R.id.default_modifiers).getItemId());
+
+        // Test shortcut trigger in case of non-default single modifier
+        keyCodeToSend = KeyEvent.KEYCODE_C;
+        metaState = KeyEvent.META_SHIFT_ON;
+        keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+                keyCodeToSend, 0, metaState);
+        assertTrue(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+        assertEquals(mActivity.getMenuItemIdTracker(),
+                mMenu.findItem(R.id.single_modifier).getItemId());
+
+        // Test shortcut trigger in case of multiple modifiers
+        keyCodeToSend = KeyEvent.KEYCODE_D;
+        metaState = KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON;
+        keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+                keyCodeToSend, 0, metaState);
+        assertTrue(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+        assertEquals(mActivity.getMenuItemIdTracker(),
+                mMenu.findItem(R.id.multiple_modifiers).getItemId());
+
+        // Test no shortcut trigger in case of incorrect modifier
+        keyCodeToSend = KeyEvent.KEYCODE_E;
+        metaState = KeyEvent.META_CTRL_ON;
+        keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+                keyCodeToSend, 0, metaState);
+        assertFalse(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/MenuTestActivity.java b/tests/tests/view/src/android/view/cts/MenuTestActivity.java
new file mode 100644
index 0000000..d5d5c4a
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/MenuTestActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import android.app.Activity;
+import android.view.MenuItem;
+
+public class MenuTestActivity extends Activity {
+
+    private int mMenuItemIdTracker;
+
+    public int getMenuItemIdTracker() {
+        return mMenuItemIdTracker;
+    }
+
+    public boolean handleMenuItem(MenuItem item) {
+        mMenuItemIdTracker = item.getItemId();
+        return true;
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/ViewGroupTest.java b/tests/tests/view/src/android/view/cts/ViewGroupTest.java
index 5b8d17f..f051a27 100644
--- a/tests/tests/view/src/android/view/cts/ViewGroupTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewGroupTest.java
@@ -29,6 +29,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.NonNull;
+import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.XmlResourceParser;
@@ -45,6 +47,7 @@
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -69,11 +72,13 @@
 import android.view.animation.RotateAnimation;
 import android.view.animation.Transformation;
 import android.view.cts.util.XmlUtils;
+import android.widget.Button;
 import android.widget.TextView;
 
 import com.android.compatibility.common.util.CTSResult;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
@@ -91,6 +96,10 @@
     private TextView mTextView;
     private MockTextView mMockTextView;
 
+    @Rule
+    public ActivityTestRule<CtsActivity> mCtsActivityRule =
+            new ActivityTestRule<>(CtsActivity.class, false, false);
+
     private final Sync mSync = new Sync();
     private static class Sync {
         boolean mHasNotify;
@@ -973,6 +982,43 @@
         assertEquals(CTSResult.RESULT_OK, mResultCode);
     }
 
+    @Test
+    public void testOnDescendantInvalidated() throws Throwable {
+        Activity activity = null;
+        try {
+            activity = mCtsActivityRule.launchActivity(new Intent());
+
+            mCtsActivityRule.runOnUiThread(() -> {
+                View child = mTextView;
+                MockViewGroup parent = mMockViewGroup;
+                MockViewGroup grandParent = new MockViewGroup(mContext);
+                parent.addView(child);
+                grandParent.addView(parent);
+                mCtsActivityRule.getActivity().setContentView(grandParent);
+
+                parent.isOnDescendantInvalidatedCalled = false;
+                grandParent.isOnDescendantInvalidatedCalled = false;
+
+                parent.invalidateChild(child, new Rect(0, 0, 1, 1));
+
+                assertTrue(parent.isOnDescendantInvalidatedCalled);
+                assertTrue(grandParent.isOnDescendantInvalidatedCalled);
+
+                parent.isOnDescendantInvalidatedCalled = false;
+                grandParent.isOnDescendantInvalidatedCalled = false;
+
+                grandParent.invalidateChild(child, new Rect(0, 0, 1, 1));
+
+                assertFalse(parent.isOnDescendantInvalidatedCalled);
+                assertTrue(grandParent.isOnDescendantInvalidatedCalled);
+            });
+        } finally {
+            if (activity != null) {
+                activity.finish();
+            }
+        }
+    }
+
     private void waitForResult() {
         synchronized (mSync) {
             while(!mSync.mHasNotify) {
@@ -1444,78 +1490,175 @@
         assertTrue(mMockViewGroup.isOnRequestFocusInDescendantsCalled);
     }
 
-    private void setupRestoreDefaultFocus() {
-        // Mark 2 children as focusable and add to the parent, then mark the second one as focused
-        // by default.
-        mMockViewGroup = new MockViewGroup(mContext);
-        mMockTextView = new MockTextView(mContext);
-        mMockTextView.setFocusable(true);
-        mTextView = new TextView(mContext);
-        mTextView.setFocusable(true);
-        mMockViewGroup.addView(mMockTextView);
-        mMockViewGroup.addView(mTextView);
-        mTextView.setFocusedByDefault(true);
+    private class TestClusterHier {
+        public MockViewGroup top = new MockViewGroup(mContext);
+        public MockViewGroup cluster1 = new MockViewGroup(mContext);
+        public Button c1view1 = new Button(mContext);
+        public Button c1view2 = new Button(mContext);
+        public MockViewGroup cluster2 = new MockViewGroup(mContext);
+        public MockViewGroup nestedGroup = new MockViewGroup(mContext);
+        public Button c2view1 = new Button(mContext);
+        public Button c2view2 = new Button(mContext);
+        TestClusterHier() {
+            for (Button bt : new Button[]{c1view1, c1view2, c2view1, c2view2}) {
+                // Otherwise this test won't work during suite-run.
+                bt.setFocusableInTouchMode(true);
+            }
+            cluster1.setKeyboardNavigationCluster(true);
+            cluster2.setKeyboardNavigationCluster(true);
+            cluster1.addView(c1view1);
+            cluster1.addView(c1view2);
+            cluster2.addView(c2view1);
+            nestedGroup.addView(c2view2);
+            cluster2.addView(nestedGroup);
+            top.addView(cluster1);
+            top.addView(cluster2);
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    public void testRestoreFocusInCluster() {
+        TestClusterHier h = new TestClusterHier();
+        h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c1view1, h.top.findFocus());
+
+        h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c2view1, h.top.findFocus());
+
+        h.c2view2.setFocusedInCluster();
+        h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c2view2, h.top.findFocus());
+        h.c2view1.setFocusedInCluster();
+        h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c2view1, h.top.findFocus());
+
+        h.c1view2.setFocusedInCluster();
+        h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c1view2, h.top.findFocus());
+
+        h = new TestClusterHier();
+        h.cluster1.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertNull(h.top.findFocus());
+
+        h.c2view1.setVisibility(View.INVISIBLE);
+        h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c2view2, h.top.findFocus());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFocusInClusterRemovals() {
+        // Removing focused-in-cluster view from its parent in various ways.
+        TestClusterHier h = new TestClusterHier();
+        h.c1view1.setFocusedInCluster();
+        h.cluster1.removeView(h.c1view1);
+        h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c1view2, h.cluster1.findFocus());
+
+        h = new TestClusterHier();
+        h.c1view1.setFocusedInCluster();
+        h.cluster1.removeViews(0, 1);
+        h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c1view2, h.cluster1.findFocus());
+
+        h = new TestClusterHier();
+        h.c2view1.setFocusedInCluster();
+        h.cluster2.removeAllViewsInLayout();
+        h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertNull(h.cluster2.findFocus());
+
+        h = new TestClusterHier();
+        h.c1view1.setFocusedInCluster();
+        h.cluster1.detachViewFromParent(h.c1view1);
+        h.cluster1.attachViewToParent(h.c1view1, 1, null);
+        h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c1view1, h.cluster1.findFocus());
+
+        h = new TestClusterHier();
+        h.c1view1.setFocusedInCluster();
+        h.cluster1.detachViewFromParent(h.c1view1);
+        h.cluster1.removeDetachedView(h.c1view1, false);
+        h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c1view2, h.cluster1.findFocus());
     }
 
     @UiThreadTest
     @Test
     public void testRestoreDefaultFocus() {
-        // Invoking restoreDefaultFocus with various conditions that affect the outcome.
-        setupRestoreDefaultFocus();
-        mTextView.setFocusedByDefault(false);
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(mMockTextView, mMockViewGroup.findFocus());
+        TestClusterHier h = new TestClusterHier();
+        h.c1view2.setFocusedByDefault(true);
+        h.top.restoreDefaultFocus();
+        assertSame(h.c1view2, h.top.findFocus());
 
-        setupRestoreDefaultFocus();
-        mTextView.setKeyboardNavigationCluster(true);
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(mMockTextView, mMockViewGroup.findFocus());
+        h.c1view2.setFocusedByDefault(false);
+        h.top.restoreDefaultFocus();
+        assertSame(h.c1view1, h.top.findFocus());
 
-        setupRestoreDefaultFocus();
-        mMockViewGroup.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(null, mMockViewGroup.findFocus());
+        // default focus favors higher-up views
+        h.c1view2.setFocusedByDefault(true);
+        h.cluster1.setFocusedByDefault(true);
+        h.top.restoreDefaultFocus();
+        assertSame(h.c1view2, h.top.findFocus());
+        h.c2view1.setFocusedByDefault(true);
+        h.top.restoreDefaultFocus();
+        assertSame(h.c1view2, h.top.findFocus());
+        h.cluster2.setFocusedByDefault(true);
+        h.cluster1.setFocusedByDefault(false);
+        h.top.restoreDefaultFocus();
+        assertSame(h.c2view1, h.top.findFocus());
 
-        setupRestoreDefaultFocus();
-        mTextView.setVisibility(View.INVISIBLE);
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(mMockTextView, mMockViewGroup.findFocus());
-
-        setupRestoreDefaultFocus();
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(mTextView, mMockViewGroup.findFocus());
+        // removing default receivers should resolve to an existing default
+        h = new TestClusterHier();
+        h.c1view2.setFocusedByDefault(true);
+        h.cluster1.setFocusedByDefault(true);
+        h.c2view2.setFocusedByDefault(true);
+        h.top.restoreDefaultFocus();
+        assertSame(h.c1view2, h.top.findFocus());
+        h.c1view2.setFocusedByDefault(false);
+        h.cluster1.setFocusedByDefault(false);
+        // only 1 focused-by-default view left, but its in a different branch. Should still pull
+        // default focus.
+        h.top.restoreDefaultFocus();
+        assertSame(h.c2view2, h.top.findFocus());
     }
 
     @UiThreadTest
     @Test
     public void testDefaultFocusViewRemoved() {
         // Removing default-focus view from its parent in various ways.
-        setupRestoreDefaultFocus();
-        mMockViewGroup.removeView(mTextView);
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(mMockTextView, mMockViewGroup.findFocus());
+        TestClusterHier h = new TestClusterHier();
+        h.c1view1.setFocusedByDefault(true);
+        h.cluster1.removeView(h.c1view1);
+        h.cluster1.restoreDefaultFocus();
+        assertSame(h.c1view2, h.cluster1.findFocus());
 
-        setupRestoreDefaultFocus();
-        mMockViewGroup.removeViews(1, 1);
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(mMockTextView, mMockViewGroup.findFocus());
+        h = new TestClusterHier();
+        h.c1view1.setFocusedByDefault(true);
+        h.cluster1.removeViews(0, 1);
+        h.cluster1.restoreDefaultFocus();
+        assertSame(h.c1view2, h.cluster1.findFocus());
 
-        setupRestoreDefaultFocus();
-        mMockViewGroup.removeAllViewsInLayout();
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(null, mMockViewGroup.findFocus());
+        h = new TestClusterHier();
+        h.c1view1.setFocusedByDefault(true);
+        h.cluster1.removeAllViewsInLayout();
+        h.cluster1.restoreDefaultFocus();
+        assertNull(h.cluster1.findFocus());
 
-        setupRestoreDefaultFocus();
-        mMockViewGroup.detachViewFromParent(mTextView);
-        mMockViewGroup.attachViewToParent(mTextView, 1, null);
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(mTextView, mMockViewGroup.findFocus());
+        h = new TestClusterHier();
+        h.c1view1.setFocusedByDefault(true);
+        h.cluster1.detachViewFromParent(h.c1view1);
+        h.cluster1.attachViewToParent(h.c1view1, 1, null);
+        h.cluster1.restoreDefaultFocus();
+        assertSame(h.c1view1, h.cluster1.findFocus());
 
-        setupRestoreDefaultFocus();
-        mMockViewGroup.detachViewFromParent(mTextView);
-        mMockViewGroup.removeDetachedView(mTextView, false);
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
-        assertSame(mMockTextView, mMockViewGroup.findFocus());
+        h = new TestClusterHier();
+        h.c1view1.setFocusedByDefault(true);
+        h.cluster1.detachViewFromParent(h.c1view1);
+        h.cluster1.removeDetachedView(h.c1view1, false);
+        h.cluster1.restoreDefaultFocus();
+        assertSame(h.c1view2, h.cluster1.findFocus());
     }
 
     @UiThreadTest
@@ -1527,15 +1670,34 @@
         mMockTextView.setFocusable(true);
         mTextView = new TextView(mContext);
         mTextView.setFocusable(true);
+        mTextView.setFocusableInTouchMode(true);
         mTextView.setFocusedByDefault(true);
         mMockViewGroup.addView(mMockTextView);
         mMockViewGroup.addView(mTextView);
-        mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
+        mMockViewGroup.restoreDefaultFocus();
         assertTrue(mTextView.isFocused());
     }
 
     @UiThreadTest
     @Test
+    public void testDefaultFocusWorksForClusters() {
+        TestClusterHier h = new TestClusterHier();
+        h.c2view2.setFocusedByDefault(true);
+        h.cluster1.setFocusedByDefault(true);
+        h.top.restoreDefaultFocus();
+        assertSame(h.c1view1, h.top.findFocus());
+        h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c2view2, h.top.findFocus());
+
+        // make sure focused in cluster takes priority in cluster-focus
+        h.c1view2.setFocusedByDefault(true);
+        h.c1view1.setFocusedInCluster();
+        h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+        assertSame(h.c1view1, h.top.findFocus());
+    }
+
+    @UiThreadTest
+    @Test
     public void testRequestTransparentRegion() {
         MockViewGroup parent = new MockViewGroup(mContext);
         MockView child1 = new MockView(mContext);
@@ -2401,6 +2563,7 @@
         public boolean isDrawableStateChangedCalled;
         public boolean isRequestLayoutCalled;
         public boolean isOnLayoutCalled;
+        public boolean isOnDescendantInvalidatedCalled;
         public int left;
         public int top;
         public int right;
@@ -2760,6 +2923,12 @@
         public boolean isChildrenDrawnWithCacheEnabled() {
             return super.isChildrenDrawnWithCacheEnabled();
         }
+
+        @Override
+        public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
+            isOnDescendantInvalidatedCalled = true;
+            super.onDescendantInvalidated(child, target);
+        }
     }
 
     static class MockView2 extends View {
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 472d660..e874aef 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -2872,7 +2872,7 @@
     @Test
     public void testRestoreDefaultFocus() {
         MockView view = new MockView(mActivity);
-        view.restoreDefaultFocus(0);
+        view.restoreDefaultFocus();
         assertTrue(view.hasCalledRequestFocus());
     }
 
diff --git a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
index dd83c16..53b24d8 100644
--- a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
+++ b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
@@ -31,6 +31,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import android.widget.FrameLayout;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -254,4 +255,46 @@
         auto.setFocusable(false);
         assertSame(View.NOT_FOCUSABLE, auto.getFocusable());
     }
+
+    @UiThreadTest
+    @Test
+    public void testHasFocusable() {
+        final Activity activity = mActivityRule.getActivity();
+        final ViewGroup group = (ViewGroup) activity.findViewById(R.id.auto_test_area);
+
+        View singleView = new View(activity);
+        group.addView(singleView);
+
+        testHasFocusable(singleView);
+
+        group.removeView(singleView);
+
+        View groupView = new FrameLayout(activity);
+        group.addView(groupView);
+
+        testHasFocusable(groupView);
+    }
+
+    private void testHasFocusable(View view) {
+        assertEquals("single view was not auto-focusable", View.FOCUSABLE_AUTO,
+                view.getFocusable());
+        assertFalse("single view unexpectedly hasFocusable", view.hasFocusable());
+        assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable());
+
+        view.setClickable(true);
+        assertTrue("single view doesn't hasFocusable", view.hasFocusable());
+        assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable());
+
+        view.setClickable(false);
+        assertFalse("single view unexpectedly hasFocusable", view.hasFocusable());
+        assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable());
+
+        view.setFocusable(View.NOT_FOCUSABLE);
+        assertFalse("single view unexpectedly hasFocusable", view.hasFocusable());
+        assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable());
+
+        view.setFocusable(View.FOCUSABLE);
+        assertTrue("single view doesn't hasFocusable", view.hasFocusable());
+        assertTrue("single view doesn't hasExplicitFocusable", view.hasExplicitFocusable());
+    }
 }
diff --git a/tests/tests/widget/res/font/samplefont.ttf b/tests/tests/widget/res/font/samplefont.ttf
new file mode 100644
index 0000000..49f1c62
--- /dev/null
+++ b/tests/tests/widget/res/font/samplefont.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/samplexmlfont.xml b/tests/tests/widget/res/font/samplexmlfont.xml
new file mode 100644
index 0000000..2905c13
--- /dev/null
+++ b/tests/tests/widget/res/font/samplexmlfont.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/samplefont" />
+</font-family>
\ No newline at end of file
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index 1af5557..6e311d6 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -287,6 +287,63 @@
                 android:autoSizeMaxTextSize="50dp"
                 android:autoSizeStepGranularity="1dp" />
 
+            <TextView
+                android:id="@+id/textview_fontresource_fontfamily"
+                android:text="@string/text_view_hello"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:fontFamily="@font/samplefont" />
+
+            <TextView
+                android:id="@+id/textview_fontxmlresource_fontfamily"
+                android:text="@string/text_view_hello"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:fontFamily="@font/samplexmlfont" />
+
+            <TextView
+                android:id="@+id/textview_fontresource_style"
+                android:text="@string/text_view_hello"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="@style/TextView_FontResource" />
+
+            <TextView
+                android:id="@+id/textview_fontxmlresource_style"
+                android:text="@string/text_view_hello"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="@style/TextView_FontXmlResource" />
+
+            <TextView
+                android:id="@+id/textview_fontresource_textAppearance"
+                android:text="@string/text_view_hello"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="@style/TextView_FontResource" />
+
+            <TextView
+                android:id="@+id/textview_fontxmlresource_textAppearance"
+                android:text="@string/text_view_hello"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="@style/TextView_FontXmlResource" />
+
+            <TextView
+                android:id="@+id/textview_autosize_xy_predef_sizes"
+                android:layout_width="100dp"
+                android:layout_height="200dp"
+                android:text="@string/sample_text"
+                android:autoSizeText="xy"
+                android:autoSizeStepSizeSet="@array/auto_size_predefined_sizes" />
+
+            <TextView
+                android:id="@+id/textview_autosize_xy_predef_sizes_redundant_values"
+                android:layout_width="100dp"
+                android:layout_height="200dp"
+                android:text="@string/sample_text"
+                android:autoSizeText="xy"
+                android:autoSizeStepSizeSet="@array/auto_size_predefined_sizes_redundant_values" />
         </LinearLayout>
 
 </ScrollView>
diff --git a/tests/tests/widget/res/values/arrays.xml b/tests/tests/widget/res/values/arrays.xml
index 71e0133..45d3a1d 100644
--- a/tests/tests/widget/res/values/arrays.xml
+++ b/tests/tests/widget/res/values/arrays.xml
@@ -54,4 +54,21 @@
         <item>9</item>
         <item>10</item>
     </integer-array>
+
+    <array name="auto_size_predefined_sizes">
+        <item>10px</item>
+        <item>10dip</item>
+        <item>10sp</item>
+        <item>10pt</item>
+        <item>10in</item>
+        <item>10mm</item>
+    </array>
+
+    <array name="auto_size_predefined_sizes_redundant_values">
+        <item>40px</item>
+        <item>10px</item>
+        <item>10px</item>
+        <item>10px</item>
+        <item>0dp</item>
+    </array>
 </resources>
diff --git a/tests/tests/widget/res/values/styles.xml b/tests/tests/widget/res/values/styles.xml
index 7ba9711..a47f169 100644
--- a/tests/tests/widget/res/values/styles.xml
+++ b/tests/tests/widget/res/values/styles.xml
@@ -227,4 +227,13 @@
         <!-- Force swipe-to-dismiss to false. -->
         <item name="android:windowSwipeToDismiss">false</item>
     </style>
+
+    <style name="TextView_FontResource">
+        <item name="android:fontFamily">@font/samplefont</item>
+        <item name="android:textAppearance">@null</item>
+    </style>
+
+    <style name="TextView_FontXmlResource">
+        <item name="android:fontFamily">@font/samplexmlfont</item>
+    </style>
 </resources>
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 46755c0..d360ace 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -16,6 +16,7 @@
 
 package android.widget.cts;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -156,6 +157,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Locale;
 
 /**
@@ -3361,6 +3363,22 @@
                 "\"smcp\" on", mTextView.getFontFeatureSettings());
     }
 
+    @UiThreadTest
+    @Test
+    public void testSetGetFontVariationSettings() {
+        mTextView = new TextView(mActivity);
+
+        // The default font variation settings should be null.
+        assertNull(mTextView.getFontVariationSettings());
+
+        final String setting = "'wdth' 2.0";
+        mTextView.setFontVariationSettings(setting);
+        assertEquals(setting, mTextView.getFontVariationSettings());
+
+        mTextView.setFontVariationSettings("");
+        assertNull(mTextView.getFontVariationSettings());
+    }
+
     @Test
     public void testGetOffsetForPositionSingleLineLtr() throws Throwable {
         // asserts getOffsetPosition returns correct values for a single line LTR text
@@ -6224,7 +6242,6 @@
         ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
         CtsTouchUtils.emulateTapOnView(mInstrumentation, mTextView, spanDetails.mXPosInside,
                 spanDetails.mYPosInside);
-        SystemClock.sleep(ViewConfiguration.getDoubleTapTimeout() + 1);
         verify(spanDetails.mClickableSpan, times(1)).onClick(mTextView);
     }
 
@@ -6233,7 +6250,7 @@
         ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
         CtsTouchUtils.emulateDoubleTapOnView(mInstrumentation, mTextView, spanDetails.mXPosInside,
                 spanDetails.mYPosInside);
-        verify(spanDetails.mClickableSpan, never()).onClick(mTextView);
+        verify(spanDetails.mClickableSpan, times(2)).onClick(mTextView);
     }
 
     @Test
@@ -6241,7 +6258,6 @@
         ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
         CtsTouchUtils.emulateTapOnView(mInstrumentation, mTextView, spanDetails.mXPosOutside,
                 spanDetails.mYPosOutside);
-        SystemClock.sleep(ViewConfiguration.getDoubleTapTimeout() + 1);
         verify(spanDetails.mClickableSpan, never()).onClick(mTextView);
     }
 
@@ -6643,7 +6659,137 @@
     }
 
     @Test
-    public void testAutoSizeXY_getSetAutoSizeTextXY_defaults() {
+    public void testAutoSizeXY_obtainStyledAttributesUsingPredefinedSizes() {
+        DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics();
+        final TextView autoSizeTextViewXY = (TextView) mActivity.findViewById(
+                R.id.textview_autosize_xy_predef_sizes);
+
+        // In arrays.xml predefined the step sizes as: 10px, 10dp, 10sp, 10pt, 10in and 10mm.
+        int[] expectedSizes = new int[] {
+                (int) Math.ceil(
+                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 10f, metrics)),
+                (int) Math.ceil(
+                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, metrics)),
+                (int) Math.ceil(
+                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, metrics)),
+                (int) Math.ceil(
+                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, 10f, metrics)),
+                (int) Math.ceil(
+                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_IN, 10f, metrics)),
+                (int) Math.ceil(
+                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10f, metrics))};
+        expectedSizes = Arrays.stream(expectedSizes)
+                .filter(x -> x > 0)
+                .distinct()
+                .sorted()
+                .toArray();
+        assertArrayEquals(expectedSizes, autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+
+        boolean containsValueFromExpectedSizes = false;
+        int textSize = (int) autoSizeTextViewXY.getTextSize();
+        for (int i = 0; i < expectedSizes.length; i++) {
+            if (expectedSizes[i] == textSize) {
+                containsValueFromExpectedSizes = true;
+                break;
+            }
+        }
+        assertTrue(containsValueFromExpectedSizes);
+    }
+
+    @Test
+    public void testAutoSizeXY_obtainStyledAttributesPredefinedSizesFiltering() {
+        TextView autoSizeTextViewXY = (TextView) mActivity.findViewById(
+                R.id.textview_autosize_xy_predef_sizes_redundant_values);
+
+        // In arrays.xml predefined the step sizes as: 40px, 10px, 10px, 10px, 0dp.
+        final int[] expectedSizes = new int[] {10, 40};
+        assertArrayEquals(expectedSizes, autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+    }
+
+    @Test
+    public void testAutoSizeXY_predefinedSizesFilteringAndSorting() throws Throwable {
+        mTextView = findTextView(R.id.textview_text);
+        assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
+
+        final int[] predefinedSizes = new int[] {400, 0, 10, 40, 10, 10, 0, 0};
+        mActivityRule.runOnUiThread(() -> {
+            mTextView.setAutoSizeTextType(TextView.AUTO_SIZE_TEXT_TYPE_XY);
+            mTextView.setAutoSizeTextPresetSizes(predefinedSizes);
+        });
+        mInstrumentation.waitForIdleSync();
+        assertArrayEquals(new int[] {10, 40, 400}, mTextView.getAutoSizeTextPresetSizes());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testAutoSizeXY_predefinedSizesNullArray() throws Throwable {
+        mTextView = findTextView(R.id.textview_text);
+        assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
+
+        final int[] predefinedSizes = null;
+        mActivityRule.runOnUiThread(() -> {
+            mTextView.setAutoSizeTextType(TextView.AUTO_SIZE_TEXT_TYPE_XY);
+            mTextView.setAutoSizeTextPresetSizes(predefinedSizes);
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    @Test
+    public void testAutoSizeXY_predefinedSizesEmptyArray() throws Throwable {
+        mTextView = findTextView(R.id.textview_text);
+        assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
+
+        mActivityRule.runOnUiThread(() ->
+                mTextView.setAutoSizeTextType(TextView.AUTO_SIZE_TEXT_TYPE_XY));
+        mInstrumentation.waitForIdleSync();
+
+        final int[] defaultSizes = mTextView.getAutoSizeTextPresetSizes();
+        assertNotNull(defaultSizes);
+        assertTrue(defaultSizes.length > 0);
+
+        final int[] predefinedSizes = new int[0];
+        mActivityRule.runOnUiThread(() ->
+                mTextView.setAutoSizeTextPresetSizes(predefinedSizes));
+        mInstrumentation.waitForIdleSync();
+
+        final int[] newSizes = mTextView.getAutoSizeTextPresetSizes();
+        assertNotNull(defaultSizes);
+        assertArrayEquals(defaultSizes, newSizes);
+    }
+
+    @Test
+    public void testAutoSizeXY_buildsSizes() throws Throwable {
+        TextView autoSizeTextViewXY = (TextView) mActivity.findViewById(R.id.textview_autosize_xy);
+
+        // Verify that the interval limits are both included.
+        mActivityRule.runOnUiThread(() -> {
+            autoSizeTextViewXY.setAutoSizeMinTextSize(TypedValue.COMPLEX_UNIT_PX, 10);
+            autoSizeTextViewXY.setAutoSizeMaxTextSize(TypedValue.COMPLEX_UNIT_PX, 20);
+            autoSizeTextViewXY.setAutoSizeStepGranularity(TypedValue.COMPLEX_UNIT_PX, 2);
+        });
+        mInstrumentation.waitForIdleSync();
+        assertArrayEquals(
+                new int[] {10, 12, 14, 16, 18, 20},
+                autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+
+        mActivityRule.runOnUiThread(() -> {
+            autoSizeTextViewXY.setAutoSizeMaxTextSize(TypedValue.COMPLEX_UNIT_PX, 19);
+        });
+        mInstrumentation.waitForIdleSync();
+        assertArrayEquals(
+                new int[] {10, 12, 14, 16, 18},
+                autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+
+        mActivityRule.runOnUiThread(() -> {
+            autoSizeTextViewXY.setAutoSizeMaxTextSize(TypedValue.COMPLEX_UNIT_PX, 21);
+        });
+        mInstrumentation.waitForIdleSync();
+        assertArrayEquals(
+                new int[] {10, 12, 14, 16, 18, 20},
+                autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+    }
+
+    @Test
+    public void testAutoSizeXY_getSetAutoSizeTextXYDefaults() {
         final TextView textView = new TextView(mActivity);
         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, textView.getAutoSizeTextType());
         // Min/Max/Granularity values for auto-sizing are 0 because they are not used.
@@ -6655,12 +6801,10 @@
         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_XY, textView.getAutoSizeTextType());
         // Min/Max default values for auto-sizing XY have been loaded.
         final int minSize = textView.getAutoSizeMinTextSize();
-        assertNotEquals(0, minSize);
         final int maxSize = textView.getAutoSizeMaxTextSize();
-        assertNotEquals(0, maxSize);
+        assertTrue(0 < minSize);
         assertTrue(minSize < maxSize);
-        final int stepGranularity = textView.getAutoSizeStepGranularity();
-        assertNotEquals(0, stepGranularity);
+        assertNotEquals(0, textView.getAutoSizeStepGranularity());
 
         textView.setAutoSizeTextType(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, textView.getAutoSizeTextType());
@@ -6785,6 +6929,8 @@
         Point offset = getCenterPositionOfTextAt(mTextView, startIndex, endIndex);
         CtsTouchUtils.emulateLongPressOnView(mInstrumentation, mTextView, offset.x, offset.y);
         PollingCheck.waitFor(mTextView::hasSelection);
+        // Wait for smart selection. It times out after about 200ms.
+        SystemClock.sleep(300);
 
         TextSelection selection = tcm.getDefaultTextClassifier()
                 .suggestSelection(text, startIndex, endIndex);
@@ -6792,6 +6938,84 @@
         assertEquals(selection.getSelectionEndIndex(), mTextView.getSelectionEnd());
         // TODO: Test the floating toolbar content.
     }
+    @Test
+    public void testFontResources_setInXmlFamilyName() {
+        mTextView = findTextView(R.id.textview_fontresource_fontfamily);
+        Typeface expected = mActivity.getResources().getFont(R.font.samplefont);
+
+        assertEquals(expected, mTextView.getTypeface());
+    }
+
+    @Test
+    public void testFontResourcesXml_setInXmlFamilyName() {
+        mTextView = findTextView(R.id.textview_fontxmlresource_fontfamily);
+        Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
+
+        assertEquals(expected, mTextView.getTypeface());
+    }
+
+    @Test
+    public void testFontResources_setInXmlStyle() {
+        mTextView = findTextView(R.id.textview_fontresource_style);
+        Typeface expected = mActivity.getResources().getFont(R.font.samplefont);
+
+        assertEquals(expected, mTextView.getTypeface());
+    }
+
+    @Test
+    public void testFontResourcesXml_setInXmlStyle() {
+        mTextView = findTextView(R.id.textview_fontxmlresource_style);
+        Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
+
+        assertEquals(expected, mTextView.getTypeface());
+    }
+
+    @Test
+    public void testFontResources_setInXmlTextAppearance() {
+        mTextView = findTextView(R.id.textview_fontresource_textAppearance);
+        Typeface expected = mActivity.getResources().getFont(R.font.samplefont);
+
+        assertEquals(expected, mTextView.getTypeface());
+    }
+
+    @Test
+    public void testFontResourcesXml_setInXmlTextAppearance() {
+        mTextView = findTextView(R.id.textview_fontxmlresource_textAppearance);
+        Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
+
+        assertEquals(expected, mTextView.getTypeface());
+    }
+
+    @Test
+    public void testSmartSelection_multiSelect() throws Throwable {
+        mTextView = findTextView(R.id.textview_text);
+        String text = "The president-elect, Filip, is coming to town tomorrow.";
+        int startIndex = text.indexOf("is coming to town");
+        int endIndex = startIndex + "is coming to town".length();
+        initTextViewForTypingOnUiThread();
+        TextClassificationManager tcm = mActivity.getSystemService(TextClassificationManager.class);
+        mActivityRule.runOnUiThread(() -> {
+            mTextView.setTextIsSelectable(true);
+            mTextView.setText(text, BufferType.EDITABLE);
+            mTextView.setTextClassifier(tcm.getDefaultTextClassifier());
+        });
+        mInstrumentation.waitForIdleSync();
+
+        Point start = getCenterPositionOfTextAt(mTextView, startIndex, startIndex);
+        Point end = getCenterPositionOfTextAt(mTextView, endIndex, endIndex);
+        int[] viewOnScreenXY = new int[2];
+        mTextView.getLocationOnScreen(viewOnScreenXY);
+        int startX = start.x + viewOnScreenXY[0];
+        int startY = start.y + viewOnScreenXY[1];
+        int offsetX = end.x - start.x;
+
+        CtsTouchUtils.emulateLongPressAndDragGesture(
+                mInstrumentation, startX, startY, offsetX, 0 /* offsetY */);
+
+        // No smart selection when multiple words are selected.
+        assertEquals(startIndex, mTextView.getSelectionStart());
+        assertEquals(endIndex, mTextView.getSelectionEnd());
+    }
 
     /**
      * Some TextView attributes require non-fixed width and/or layout height. This function removes
diff --git a/tools/cts-tradefed/Android.mk b/tools/cts-tradefed/Android.mk
index bb4b934..7783e58c 100644
--- a/tools/cts-tradefed/Android.mk
+++ b/tools/cts-tradefed/Android.mk
@@ -25,7 +25,7 @@
 LOCAL_SUITE_TARGET_ARCH := $(TARGET_ARCH)
 LOCAL_SUITE_NAME := CTS
 LOCAL_SUITE_FULLNAME := "Compatibility Test Suite"
-LOCAL_SUITE_VERSION := 7.1_r1
+LOCAL_SUITE_VERSION := 7.1_r2
 
 LOCAL_MODULE := cts-tradefed