Merge "Add aggregated partial wakelock time per uid" into oc-dev
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 014706e..18a8764 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -3044,6 +3044,22 @@
         </service>
 
         <activity
+            android:name=".dialer.DialerShowsHunOnIncomingCallActivity"
+            android:label="@string/dialer_shows_hun_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_telephony"/>
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.telephony"/>
+        </activity>
+
+        <activity
             android:name=".voicemail.CallSettingsCheckActivity"
             android:label="@string/call_settings_check_test">
             <intent-filter>
@@ -3075,6 +3091,21 @@
                 android:value="android.hardware.telephony"/>
         </activity>
 
+        <activity
+            android:name=".dialer.DialerImplementsTelecomIntentsActivity"
+            android:label="@string/dialer_telecom_intents_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_telephony"/>
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.telephony"/>
+        </activity>
 
         <service
             android:name=".voicemail.CtsVisualVoicemailService"
diff --git a/apps/CtsVerifier/res/layout/dialer_hun_on_incoming.xml b/apps/CtsVerifier/res/layout/dialer_hun_on_incoming.xml
new file mode 100644
index 0000000..9082c3c
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/dialer_hun_on_incoming.xml
@@ -0,0 +1,100 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/dialer_shows_hun_test"/>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <ImageView
+        android:id="@+id/set_default_dialer_image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="@dimen/js_padding"
+        android:src="@drawable/fs_indeterminate"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/dialer_shows_hun_test_instructions"
+        android:textSize="16dp"/>
+  </LinearLayout>
+
+  <Button
+      android:id="@+id/set_default_dialer"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/voicemail_set_default_dialer_button"/>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <TextView
+        android:id="@+id/dialer_shows_hun_explanation"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/dialer_shows_hun_explanation"
+        android:textSize="16dp"/>
+  </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <CheckBox
+        android:id="@+id/dialer_shows_hun_check_box"
+        android:text="@string/dialer_shows_hun_check_box"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+  </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <ImageView
+        android:id="@+id/restore_default_dialer_image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="@dimen/js_padding"
+        android:src="@drawable/fs_indeterminate"/>
+    <TextView
+        android:id="@+id/restore_default_dialer_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/voicemail_restore_default_dialer_description"
+        android:textSize="16dp"/>
+  </LinearLayout>
+
+  <Button
+      android:id="@+id/restore_default_dialer"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/voicemail_restore_default_dialer_button"/>
+
+  <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/dialer_telecom_intents.xml b/apps/CtsVerifier/res/layout/dialer_telecom_intents.xml
new file mode 100644
index 0000000..49bfce2a
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/dialer_telecom_intents.xml
@@ -0,0 +1,126 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/dialer_telecom_intents_test"/>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <ImageView
+        android:id="@+id/set_default_dialer_image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="@dimen/js_padding"
+        android:src="@drawable/fs_indeterminate"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/dialer_telecom_intents_test_instructions"
+        android:textSize="16dp"/>
+  </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <TextView
+        android:id="@+id/dialer_check_intents_implemented_explanation"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/dialer_check_intents_implemented_explanation"
+        android:textSize="16dp"/>
+  </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <Button
+        android:id="@+id/dialer_telecom_intents_call_settings"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/dialer_telecom_intents_call_settings"/>
+    <CheckBox
+        android:id="@+id/dialer_telecom_intents_call_settings_check_box"
+        android:text="@string/dialer_check_intents_check_box"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+  </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <Button
+        android:id="@+id/dialer_telecom_intents_short_sms"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/dialer_telecom_intents_short_sms"/>
+    <CheckBox
+        android:id="@+id/dialer_telecom_intents_short_sms_check_box"
+        android:text="@string/dialer_check_intents_check_box"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+  </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <Button
+        android:id="@+id/dialer_telecom_intents_calling_accounts"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/dialer_telecom_intents_calling_accounts"/>
+    <CheckBox
+        android:id="@+id/dialer_telecom_intents_calling_accounts_check_box"
+        android:text="@string/dialer_check_intents_check_box"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+  </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/js_padding"
+      android:layout_marginBottom="@dimen/js_padding">
+    <Button
+        android:id="@+id/dialer_telecom_intents_accessibility_settings"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/dialer_telecom_intents_accessibility_settings"/>
+    <CheckBox
+        android:id="@+id/dialer_telecom_intents_accessibility_settings_check_box"
+        android:text="@string/dialer_check_intents_check_box"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+  </LinearLayout>
+
+  <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index d0c9f52..69b9900 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -3818,6 +3818,32 @@
     <string name="ringtone_hidden">Ringtone settings does not exist</string>
     <string name="ringtone_not_hidden">Ringtone settings exists</string>
 
+    <!-- Strings for DialerShowsHunOnIncomingCallActivity test -->
+    <string name="dialer_shows_hun_test">Dialer Shows HUN on Incoming Call</string>
+    <string name="dialer_shows_hun_test_instructions">This test verifies that the default dialer
+        shows a heads up notification on incoming call, and that only one notification is shown.
+    </string>
+    <string name="dialer_shows_hun_explanation">Call the device.
+        Check the box if the heads up notification was shown containing the text
+        \"CTS Incoming Call Notification\", and only one incoming call notification was shown.
+    </string>
+    <string name="dialer_shows_hun_check_box">HUN shown and meets criteria specified above.</string>
+    <string name="dialer_incoming_call_hun_teaser">Incoming Call</string>
+    <string name="dialer_incoming_call_hun_desc">CTS Incoming Call Notification</string>
+
+    <!-- Strings for DialerImplementsTelecomIntentsActivity test -->
+    <string name="dialer_telecom_intents_test">System Implements Telecom Intents</string>
+    <string name="dialer_telecom_intents_test_instructions">This test verifies that the system handles
+        the specified Telecom Intents.
+    </string>
+    <string name="dialer_check_intents_implemented_explanation">Press the buttons below to launch settings activities.
+    Check the associated box if the activity loads and was launched succesfully.</string>
+    <string name="dialer_telecom_intents_call_settings">Launch call settings</string>
+    <string name="dialer_telecom_intents_short_sms">Launch short sms answer settings</string>
+    <string name="dialer_telecom_intents_calling_accounts">Launch calling accounts settings</string>
+    <string name="dialer_telecom_intents_accessibility_settings">Launch accessibility settings</string>
+    <string name="dialer_check_intents_check_box">Setting Launched</string>
+
     <!-- USB Audio Peripheral Tests -->
     <string name="usbaudio_results_text">Results...</string>
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerCallTestService.java b/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerCallTestService.java
index 1b93990..add6119 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerCallTestService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerCallTestService.java
@@ -20,6 +20,7 @@
 import android.os.IBinder;
 import android.telecom.InCallService;
 import java.util.Observable;
+import android.telecom.Call;
 
 public class DialerCallTestService extends InCallService {
 
@@ -28,7 +29,6 @@
 
   @Override
   public IBinder onBind(Intent intent) {
-    getObservable().setValue(true);
     return super.onBind(intent);
   }
 
@@ -37,12 +37,23 @@
   }
 
   public static class DialerCallTestServiceObservable extends Observable {
-    private boolean onBind;
+    private boolean onIncoming;
 
-    public void setValue(boolean value) {
-      this.onBind = value;
+    public void setOnIncoming(boolean value) {
+      this.onIncoming = value;
       setChanged();
       notifyObservers(value);
     }
+
+    public boolean getOnIncoming() {
+      return this.onIncoming;
+    }
+  }
+
+  @Override
+  public void onCallAdded(Call call) {
+    if (call.getState() == Call.STATE_RINGING) {
+      getObservable().setOnIncoming(true);
+    }
   }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerImplementsTelecomIntentsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerImplementsTelecomIntentsActivity.java
new file mode 100644
index 0000000..bc5cdbc
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerImplementsTelecomIntentsActivity.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 com.android.cts.verifier.dialer;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.TelecomManager;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/** Tests that a third party dialer implements certain telecom intents. */
+public class DialerImplementsTelecomIntentsActivity extends PassFailButtons.Activity {
+
+  private Button mLaunchCallSettingsButton;
+  private CheckBox mLaunchCallSettingsCheckBox;
+  private Button mLaunchShortSmsAnswerButton;
+  private CheckBox mLaunchShortSmsAnswerCheckBox;
+  private Button mLaunchCallingAccountsSettingsButton;
+  private CheckBox mLaunchCallingAccountsSettingsCheckBox;
+  private Button mLaunchAccessibilitySettingsButton;
+  private CheckBox mLaunchAccessibilitySettingsCheckBox;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    View view = getLayoutInflater().inflate(R.layout.dialer_telecom_intents, null);
+    setContentView(view);
+    setInfoResources(
+        R.string.dialer_telecom_intents_test,
+        R.string.dialer_telecom_intents_test_instructions,
+        -1);
+    setPassFailButtonClickListeners();
+    getPassButton().setEnabled(false);
+
+    mLaunchCallSettingsCheckBox = findViewById(R.id.dialer_telecom_intents_call_settings_check_box);
+    mLaunchShortSmsAnswerCheckBox = findViewById(R.id.dialer_telecom_intents_short_sms_check_box);
+    mLaunchCallingAccountsSettingsCheckBox =
+        findViewById(R.id.dialer_telecom_intents_calling_accounts_check_box);
+    mLaunchAccessibilitySettingsCheckBox =
+        findViewById(R.id.dialer_telecom_intents_accessibility_settings_check_box);
+
+    mLaunchCallSettingsCheckBox.setOnCheckedChangeListener(
+        (CompoundButton unusedButton, boolean unusedBoolean) -> onCheckedChangeListener());
+    mLaunchShortSmsAnswerCheckBox.setOnCheckedChangeListener(
+        (CompoundButton unusedButton, boolean unusedBoolean) -> onCheckedChangeListener());
+    mLaunchCallingAccountsSettingsCheckBox.setOnCheckedChangeListener(
+        (CompoundButton unusedButton, boolean unusedBoolean) -> onCheckedChangeListener());
+    mLaunchAccessibilitySettingsCheckBox.setOnCheckedChangeListener(
+        (CompoundButton unusedButton, boolean unusedBoolean) -> onCheckedChangeListener());
+
+    mLaunchCallSettingsButton = findViewById(R.id.dialer_telecom_intents_call_settings);
+    mLaunchCallSettingsButton.setOnClickListener(
+        (View unused) -> startActivity(new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS)));
+
+    mLaunchShortSmsAnswerButton = findViewById(R.id.dialer_telecom_intents_short_sms);
+    mLaunchShortSmsAnswerButton.setOnClickListener(
+        (View unused) ->
+            startActivity(new Intent(TelecomManager.ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS)));
+
+    mLaunchCallingAccountsSettingsButton =
+        findViewById(R.id.dialer_telecom_intents_calling_accounts);
+    mLaunchCallingAccountsSettingsButton.setOnClickListener(
+        (View unused) -> startActivity(new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS)));
+
+    mLaunchAccessibilitySettingsButton =
+        findViewById(R.id.dialer_telecom_intents_accessibility_settings);
+    mLaunchAccessibilitySettingsButton.setOnClickListener(
+        (View unused) ->
+            startActivity(new Intent(TelecomManager.ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS)));
+  }
+
+  private void onCheckedChangeListener() {
+    if (mLaunchCallSettingsCheckBox.isChecked()
+        && mLaunchShortSmsAnswerCheckBox.isChecked()
+        && mLaunchCallingAccountsSettingsCheckBox.isChecked()
+        && mLaunchAccessibilitySettingsCheckBox.isChecked()) {
+      getPassButton().setEnabled(true);
+    } else {
+      getPassButton().setEnabled(false);
+    }
+  }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerIncomingCallTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerIncomingCallTestActivity.java
index b4d040d..5ce280d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerIncomingCallTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerIncomingCallTestActivity.java
@@ -52,10 +52,12 @@
 
   @Override
   public void update(Observable observable, Object data) {
-    getPassButton().setEnabled(true);
-    mDialerIncomingCallStatusIcon.setImageDrawable(getDrawable(R.drawable.fs_good));
-    mDialerIncomingCallInstructions.setText(R.string.dialer_incoming_call_detected);
-    mDefaultDialerChanger.setRestorePending(true);
+    if (DialerCallTestService.getObservable().getOnIncoming()) {
+      getPassButton().setEnabled(true);
+      mDialerIncomingCallStatusIcon.setImageDrawable(getDrawable(R.drawable.fs_good));
+      mDialerIncomingCallInstructions.setText(R.string.dialer_incoming_call_detected);
+      mDefaultDialerChanger.setRestorePending(true);
+    }
   }
 
   @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerShowsHunOnIncomingCallActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerShowsHunOnIncomingCallActivity.java
new file mode 100644
index 0000000..1dd521f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/dialer/DialerShowsHunOnIncomingCallActivity.java
@@ -0,0 +1,101 @@
+/*
+ * 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.dialer;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.voicemail.DefaultDialerChanger;
+import java.util.Observable;
+import java.util.Observer;
+
+/** Tests that a third party dialer can show a heads up notification on an incoming call. */
+public class DialerShowsHunOnIncomingCallActivity extends PassFailButtons.Activity
+    implements Observer {
+
+  private CheckBox mConfirmHunShown;
+  private DefaultDialerChanger mDefaultDialerChanger;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    View view = getLayoutInflater().inflate(R.layout.dialer_hun_on_incoming, null);
+    setContentView(view);
+    setInfoResources(
+        R.string.dialer_shows_hun_test, R.string.dialer_shows_hun_test_instructions, -1);
+    setPassFailButtonClickListeners();
+    getPassButton().setEnabled(false);
+
+    mConfirmHunShown = findViewById(R.id.dialer_shows_hun_check_box);
+
+    mConfirmHunShown.setOnCheckedChangeListener(
+        (CompoundButton unusedButton, boolean unusedBoolean) -> onCheckedChangeListener());
+    DialerCallTestService.getObservable().addObserver(this);
+    mDefaultDialerChanger = new DefaultDialerChanger(this);
+  }
+
+  @Override
+  public void update(Observable observable, Object data) {
+    if (DialerCallTestService.getObservable().getOnIncoming()) {
+      NotificationManager notificationManager =
+          (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+      String id = "dialer_hun_test";
+      NotificationChannel channel =
+          new NotificationChannel(id, "incoming_call", NotificationManager.IMPORTANCE_MAX);
+      channel.enableLights(true);
+      channel.enableVibration(true);
+      channel.setShowBadge(false);
+      notificationManager.createNotificationChannel(channel);
+
+      Notification notification =
+          new Notification.Builder(DialerShowsHunOnIncomingCallActivity.this)
+              .setContentTitle(getResources().getString(R.string.dialer_incoming_call_hun_teaser))
+              .setContentText(getResources().getString(R.string.dialer_incoming_call_hun_desc))
+              .setSmallIcon(R.drawable.fs_good)
+              .setChannelId(id)
+              .build();
+
+      int notifyID = 1;
+      notificationManager.notify(notifyID, notification);
+    }
+  }
+
+  private void onCheckedChangeListener() {
+    mDefaultDialerChanger.setRestorePending(true);
+    if (mConfirmHunShown.isChecked()) {
+      getPassButton().setEnabled(true);
+    } else {
+      getPassButton().setEnabled(false);
+    }
+  }
+
+  @Override
+  protected void onDestroy() {
+    mDefaultDialerChanger.destroy();
+    DialerCallTestService.getObservable().deleteObserver(this);
+    super.onDestroy();
+  }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java
index 175dfb3..1727a3a 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java
@@ -19,7 +19,11 @@
 import android.content.res.AssetFileDescriptor;
 import android.drm.DrmConvertedStatus;
 import android.drm.DrmManagerClient;
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.media.Image.Plane;
 import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecInfo.VideoCapabilities;
@@ -35,10 +39,14 @@
 import com.android.compatibility.common.util.ResultUnit;
 
 import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+
 import static java.lang.reflect.Modifier.isPublic;
 import static java.lang.reflect.Modifier.isStatic;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 
 import static junit.framework.Assert.assertTrue;
@@ -425,16 +433,7 @@
     public static boolean hasCodecsForPath(Context context, String path) {
         MediaExtractor ex = null;
         try {
-            ex = new MediaExtractor();
-            Uri uri = Uri.parse(path);
-            String scheme = uri.getScheme();
-            if (scheme == null) { // file
-                ex.setDataSource(path);
-            } else if (scheme.equalsIgnoreCase("file")) {
-                ex.setDataSource(uri.getPath());
-            } else {
-                ex.setDataSource(context, uri, null);
-            }
+            ex = getExtractorForPath(context, path);
             return hasCodecsForMedia(ex);
         } catch (IOException e) {
             Log.i(TAG, "could not open path " + path);
@@ -446,6 +445,26 @@
         return true;
     }
 
+    private static MediaExtractor getExtractorForPath(Context context, String path)
+            throws IOException {
+        Uri uri = Uri.parse(path);
+        String scheme = uri.getScheme();
+        MediaExtractor ex = new MediaExtractor();
+        try {
+            if (scheme == null) { // file
+                ex.setDataSource(path);
+            } else if (scheme.equalsIgnoreCase("file")) {
+                ex.setDataSource(uri.getPath());
+            } else {
+                ex.setDataSource(context, uri, null);
+            }
+        } catch (IOException e) {
+            ex.release();
+            throw e;
+        }
+        return ex;
+    }
+
     public static boolean checkCodecsForPath(Context context, String path) {
         return check(hasCodecsForPath(context, path), "no decoder found");
     }
@@ -573,32 +592,44 @@
     }
 
     public static MediaFormat getTrackFormatForResource(
-            Context context, int resourceId, String mimeTypePrefix)
-            throws IOException {
-        MediaFormat format = null;
+            Context context,
+            int resourceId,
+            String mimeTypePrefix) throws IOException {
         MediaExtractor extractor = new MediaExtractor();
         AssetFileDescriptor afd = context.getResources().openRawResourceFd(resourceId);
         try {
-            extractor.setDataSource(
-                    afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            extractor.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
         } finally {
             afd.close();
         }
-        int trackIndex;
-        for (trackIndex = 0; trackIndex < extractor.getTrackCount(); trackIndex++) {
-            MediaFormat trackMediaFormat = extractor.getTrackFormat(trackIndex);
-            if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
-                format = trackMediaFormat;
-                break;
-            }
-        }
-        extractor.release();
-        afd.close();
-        if (format == null) {
-            throw new RuntimeException("couldn't get a track for " + mimeTypePrefix);
-        }
+        return getTrackFormatForExtractor(extractor, mimeTypePrefix);
+    }
 
-        return format;
+    public static MediaFormat getTrackFormatForPath(
+            Context context, String path, String mimeTypePrefix)
+            throws IOException {
+      MediaExtractor extractor = getExtractorForPath(context, path);
+      return getTrackFormatForExtractor(extractor, mimeTypePrefix);
+    }
+
+    private static MediaFormat getTrackFormatForExtractor(
+            MediaExtractor extractor,
+            String mimeTypePrefix) {
+      int trackIndex;
+      MediaFormat format = null;
+      for (trackIndex = 0; trackIndex < extractor.getTrackCount(); trackIndex++) {
+          MediaFormat trackMediaFormat = extractor.getTrackFormat(trackIndex);
+          if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
+              format = trackMediaFormat;
+              break;
+          }
+      }
+      extractor.release();
+      if (format == null) {
+          throw new RuntimeException("couldn't get a track for " + mimeTypePrefix);
+      }
+
+      return format;
     }
 
     public static MediaExtractor createMediaExtractorForMimeType(
@@ -1005,6 +1036,177 @@
         }
     }
 
+    /**
+     * @param decoder new MediaCodec object
+     * @param ex MediaExtractor after setDataSource and selectTrack
+     * @param frameMD5Sums reference MD5 checksum for decoded frames
+     * @return true if decoded frames checksums matches reference checksums
+     * @throws IOException
+     */
+    public static boolean verifyDecoder(
+            MediaCodec decoder, MediaExtractor ex, List<String> frameMD5Sums)
+            throws IOException {
+
+        int trackIndex = ex.getSampleTrackIndex();
+        MediaFormat format = ex.getTrackFormat(trackIndex);
+        decoder.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+        decoder.start();
+
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        final long kTimeOutUs = 5000; // 5ms timeout
+        int decodedFrameCount = 0;
+        int expectedFrameCount = frameMD5Sums.size();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+
+        while (!sawOutputEOS) {
+            // handle input
+            if (!sawInputEOS) {
+                int inIdx = decoder.dequeueInputBuffer(kTimeOutUs);
+                if (inIdx >= 0) {
+                    ByteBuffer buffer = decoder.getInputBuffer(inIdx);
+                    int sampleSize = ex.readSampleData(buffer, 0);
+                    if (sampleSize < 0) {
+                        final int flagEOS = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                        decoder.queueInputBuffer(inIdx, 0, 0, 0, flagEOS);
+                        sawInputEOS = true;
+                    } else {
+                        decoder.queueInputBuffer(inIdx, 0, sampleSize, ex.getSampleTime(), 0);
+                        ex.advance();
+                    }
+                }
+            }
+
+            // handle output
+            int outputBufIndex = decoder.dequeueOutputBuffer(info, kTimeOutUs);
+            if (outputBufIndex >= 0) {
+                try {
+                    if (info.size > 0) {
+                        // Disregard 0-sized buffers at the end.
+                        String md5CheckSum = "";
+                        Image image = decoder.getOutputImage(outputBufIndex);
+                        md5CheckSum = getImageMD5Checksum(image);
+
+                        if (!md5CheckSum.equals(frameMD5Sums.get(decodedFrameCount))) {
+                            Log.d(TAG,
+                                    String.format(
+                                            "Frame %d md5sum mismatch: %s(actual) vs %s(expected)",
+                                            decodedFrameCount, md5CheckSum,
+                                            frameMD5Sums.get(decodedFrameCount)));
+                            return false;
+                        }
+
+                        decodedFrameCount++;
+                    }
+                } catch (Exception e) {
+                    Log.e(TAG, "getOutputImage md5CheckSum failed", e);
+                    return false;
+                } finally {
+                    decoder.releaseOutputBuffer(outputBufIndex, false /* render */);
+                }
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    sawOutputEOS = true;
+                }
+            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat decOutputFormat = decoder.getOutputFormat();
+                Log.d(TAG, "output format " + decOutputFormat);
+            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                Log.i(TAG, "Skip handling MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED");
+            } else if (outputBufIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                continue;
+            } else {
+                Log.w(TAG, "decoder.dequeueOutputBuffer() unrecognized index: " + outputBufIndex);
+                return false;
+            }
+        }
+
+        if (decodedFrameCount != expectedFrameCount) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public static String getImageMD5Checksum(Image image) throws Exception {
+        int format = image.getFormat();
+        if (ImageFormat.YUV_420_888 != format) {
+            Log.w(TAG, "unsupported image format");
+            return "";
+        }
+
+        MessageDigest md = MessageDigest.getInstance("MD5");
+
+        int imageWidth = image.getWidth();
+        int imageHeight = image.getHeight();
+
+        Image.Plane[] planes = image.getPlanes();
+        for (int i = 0; i < planes.length; ++i) {
+            ByteBuffer buf = planes[i].getBuffer();
+
+            int width, height, rowStride, pixelStride, x, y;
+            rowStride = planes[i].getRowStride();
+            pixelStride = planes[i].getPixelStride();
+            if (i == 0) {
+                width = imageWidth;
+                height = imageHeight;
+            } else {
+                width = imageWidth / 2;
+                height = imageHeight /2;
+            }
+            // local contiguous pixel buffer
+            byte[] bb = new byte[width * height];
+            if (buf.hasArray()) {
+                byte b[] = buf.array();
+                int offs = buf.arrayOffset();
+                if (pixelStride == 1) {
+                    for (y = 0; y < height; ++y) {
+                        System.arraycopy(bb, y * width, b, y * rowStride + offs, width);
+                    }
+                } else {
+                    // do it pixel-by-pixel
+                    for (y = 0; y < height; ++y) {
+                        int lineOffset = offs + y * rowStride;
+                        for (x = 0; x < width; ++x) {
+                            bb[y * width + x] = b[lineOffset + x * pixelStride];
+                        }
+                    }
+                }
+            } else { // almost always ends up here due to direct buffers
+                int pos = buf.position();
+                if (pixelStride == 1) {
+                    for (y = 0; y < height; ++y) {
+                        buf.position(pos + y * rowStride);
+                        buf.get(bb, y * width, width);
+                    }
+                } else {
+                    // local line buffer
+                    byte[] lb = new byte[rowStride];
+                    // do it pixel-by-pixel
+                    for (y = 0; y < height; ++y) {
+                        buf.position(pos + y * rowStride);
+                        // we're only guaranteed to have pixelStride * (width - 1) + 1 bytes
+                        buf.get(lb, 0, pixelStride * (width - 1) + 1);
+                        for (x = 0; x < width; ++x) {
+                            bb[y * width + x] = lb[x * pixelStride];
+                        }
+                    }
+                }
+                buf.position(pos);
+            }
+            md.update(bb, 0, width * height);
+        }
+
+        return convertByteArrayToHEXString(md.digest());
+    }
+
+    private static String convertByteArrayToHEXString(byte[] ba) throws Exception {
+        StringBuilder result = new StringBuilder();
+        for (int i = 0; i < ba.length; i++) {
+            result.append(Integer.toString((ba[i] & 0xff) + 0x100, 16).substring(1));
+        }
+        return result.toString();
+    }
+
 
     /*
      *  -------------------------------------- END --------------------------------------
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 a8665b3..bd60ea9 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
@@ -85,7 +85,8 @@
 
     private static final List<String> NOT_RETRY_FILES = Arrays.asList(
             ChecksumReporter.NAME,
-            ChecksumReporter.PREV_NAME);
+            ChecksumReporter.PREV_NAME,
+            ResultHandler.FAILURE_REPORT_NAME);
 
     @Option(name = CompatibilityTest.RETRY_OPTION,
             shortName = 'r',
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 5f88bcd..4aa6e05 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -58,7 +58,7 @@
     private static final String NS = null;
     private static final String RESULT_FILE_VERSION = "5.0";
     public static final String TEST_RESULT_FILE_NAME = "test_result.xml";
-    private static final String FAILURE_REPORT_NAME = "test_result_failures.html";
+    public static final String FAILURE_REPORT_NAME = "test_result_failures.html";
     private static final String FAILURE_XSL_FILE_NAME = "compatibility_failures.xsl";
 
     public static final String[] RESULT_RESOURCES = {
diff --git a/hostsidetests/backup/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
index 4675ed0..d123a76 100644
--- a/hostsidetests/backup/AndroidTest.xml
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -17,6 +17,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsBackupRestoreDeviceApp.apk" />
+        <option name="test-file-name" value="CtsFullbackupApp.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsBackupHostTestCases.jar" />
diff --git a/hostsidetests/backup/fullbackupapp/Android.mk b/hostsidetests/backup/fullbackupapp/Android.mk
new file mode 100644
index 0000000..46af984
--- /dev/null
+++ b/hostsidetests/backup/fullbackupapp/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)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsFullbackupApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/fullbackupapp/AndroidManifest.xml b/hostsidetests/backup/fullbackupapp/AndroidManifest.xml
new file mode 100644
index 0000000..58f9306
--- /dev/null
+++ b/hostsidetests/backup/fullbackupapp/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="android.cts.backup.fullbackupapp">
+
+    <application android:label="FullbackupApp" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.fullbackupapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java b/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java
new file mode 100644
index 0000000..7c3a6f6
--- /dev/null
+++ b/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.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.cts.backup.fullbackupapp;
+
+import static android.support.test.InstrumentationRegistry.getTargetContext;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * Device side routines to be invoked by the host side NoBackupFolderHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class FullbackupTest {
+    public static final String TAG = "FullbackupCTSApp";
+    private static final int FILE_SIZE_BYTES = 1024 * 1024;
+
+    private Context mContext;
+
+    private File mNoBackupFile;
+    private File mNoBackupFile2;
+    private File mDoBackupFile;
+    private File mDoBackupFile2;
+
+    @Before
+    public void setUp() {
+        mContext = getTargetContext();
+        setupFiles();
+    }
+
+    private void setupFiles() {
+        // Files in the 'no_backup' directory. We expect these to not be backed up.
+        File noBackupDir = mContext.getNoBackupFilesDir();
+        File folderInNoBackup = new File(noBackupDir, "folder_not_to_backup");
+
+        mNoBackupFile = new File(noBackupDir, "file_not_backed_up");
+        mNoBackupFile2 = new File(folderInNoBackup, "file_not_backed_up2");
+
+        // Files in the normal files directory. These should be backed up and restored.
+        File filesDir = mContext.getFilesDir();
+        File normalFolder = new File(filesDir, "normal_folder");
+
+        mDoBackupFile = new File(filesDir, "file_to_backup");
+        mDoBackupFile2 = new File(normalFolder, "file_to_backup2");
+    }
+
+    @Test
+    public void createFiles() throws Exception {
+        // Make sure the data does not exist from before
+        deleteAllFiles();
+        checkNoFilesExist();
+
+        // Create test data
+        generateFiles();
+        checkAllFilesExist();
+
+        Log.d(TAG, "Test files created:");
+        Log.d(TAG, mNoBackupFile.getAbsolutePath());
+        Log.d(TAG, mNoBackupFile2.getAbsolutePath());
+        Log.d(TAG, mDoBackupFile.getAbsolutePath());
+        Log.d(TAG, mDoBackupFile2.getAbsolutePath());
+    }
+
+    @Test
+    public void deleteFilesAfterBackup() throws Exception {
+        // Make sure the test data exists first
+        checkAllFilesExist();
+
+        // Delete test data
+        deleteAllFiles();
+
+        // No there should be no files left
+        checkNoFilesExist();
+    }
+
+    @Test
+    public void checkRestoredFiles() throws Exception {
+        // After a restore, only files outside the 'no_backup' folder should exist
+        checkNoBackupFilesDoNotExist();
+        checkDoBackupFilesDoExist();
+    }
+
+    private void generateFiles() {
+        try {
+            // Add data to all the files we created
+            addData(mNoBackupFile);
+            addData(mNoBackupFile2);
+            addData(mDoBackupFile);
+            addData(mDoBackupFile2);
+            Log.d(TAG, "Files generated!");
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to generate files", e);
+        }
+    }
+
+    private void deleteAllFiles() {
+        mNoBackupFile.delete();
+        mNoBackupFile2.delete();
+        mDoBackupFile.delete();
+        mDoBackupFile2.delete();
+        Log.d(TAG, "Files deleted!");
+    }
+
+    private void addData(File file) throws IOException {
+        file.getParentFile().mkdirs();
+        byte[] bytes = new byte[FILE_SIZE_BYTES];
+        new Random().nextBytes(bytes);
+
+        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
+            bos.write(bytes, 0, bytes.length);
+        }
+    }
+
+    private void checkAllFilesExist() {
+        assertTrue("File in 'no_backup' did not exist!", mNoBackupFile.exists());
+        assertTrue("File in folder inside 'no_backup' did not exist!", mNoBackupFile2.exists());
+        assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
+        assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
+    }
+
+    private void checkNoFilesExist() {
+        assertFalse("File in 'no_backup' did exist!", mNoBackupFile.exists());
+        assertFalse("File in folder inside 'no_backup' did exist!", mNoBackupFile2.exists());
+        assertFalse("File in 'files' did exist!", mDoBackupFile.exists());
+        assertFalse("File in folder inside 'files' did exist!", mDoBackupFile2.exists());
+    }
+
+    private void checkNoBackupFilesDoNotExist() {
+        assertFalse("File in 'no_backup' did exist!", mNoBackupFile.exists());
+        assertFalse("File in folder inside 'no_backup' did exist!", mNoBackupFile2.exists());
+    }
+
+    private void checkDoBackupFilesDoExist() {
+        assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
+        assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
+    }
+}
diff --git a/hostsidetests/backup/src/android/backup/cts/BackupRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
similarity index 73%
rename from hostsidetests/backup/src/android/backup/cts/BackupRestoreHostSideTest.java
rename to hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
index 86cecbb..7c2f25f 100644
--- a/hostsidetests/backup/src/android/backup/cts/BackupRestoreHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
@@ -14,22 +14,17 @@
  * limitations under the License
  */
 
-package android.backup.cts.backup;
+package android.cts.backup;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
-import static org.junit.Assume.assumeTrue;
-
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.log.LogUtil.CLog;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
@@ -37,8 +32,6 @@
 import java.util.Map;
 import java.util.Scanner;
 import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Test for checking that key/value backup and restore works correctly.
@@ -49,14 +42,7 @@
  * NB: The tests uses "bmgr backupnow" for backup, which works on N+ devices.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class BackupRestoreHostSideTest extends CompatibilityHostTestBase {
-
-    /** Value of PackageManager.FEATURE_BACKUP */
-    private static final String FEATURE_BACKUP = "android.software.backup";
-
-    private static final String LOCAL_TRANSPORT =
-            "android/com.android.internal.backup.LocalTransport";
-
+public class BackupRestoreHostSideTest extends BaseBackupHostSideTest {
     /** The name of the APK of the app under test */
     private static final String TEST_APP_APK = "CtsBackupRestoreDeviceApp.apk";
 
@@ -122,24 +108,6 @@
      */
     private Map<String, String> mSavedValues;
 
-    @Before
-    public void setUp() throws DeviceNotAvailableException, Exception {
-        mIsBackupSupported = mDevice.hasFeature("feature:" + FEATURE_BACKUP);
-        assumeTrue(mIsBackupSupported);
-        // Enable backup and select local backup transport
-        assertTrue("LocalTransport should be available.", hasBackupTransport(LOCAL_TRANSPORT));
-        mWasBackupEnabled = enableBackup(true);
-        mOldTransport = setBackupTransport(LOCAL_TRANSPORT);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mIsBackupSupported) {
-            setBackupTransport(mOldTransport);
-            enableBackup(mWasBackupEnabled);
-        }
-    }
-
     @Test
     public void testKeyValueBackupAndRestore() throws Exception {
         // Clear app data if any
@@ -157,10 +125,9 @@
 
         // Run backup
         // TODO: make this compatible with N-, potentially by replacing 'backupnow' with 'run'.
-        String backupnowOutput = mDevice.executeShellCommand(
-                "bmgr backupnow " + PACKAGE_UNDER_TEST);
+        String backupnowOutput = backupNow(PACKAGE_UNDER_TEST);
 
-        assertBackupIsSuccessful(backupnowOutput);
+        assertBackupIsSuccessful(PACKAGE_UNDER_TEST, backupnowOutput);
 
         assertNull(uninstallPackage(PACKAGE_UNDER_TEST));
 
@@ -202,27 +169,6 @@
     }
 
     /**
-     * Parsing the output of "bmgr backupnow" command and checking that the package under test
-     * was backed up successfully.
-     *
-     * Expected format: "Package android.backup.cts.backuprestoreapp with result: Success"
-     */
-    private void assertBackupIsSuccessful(String backupnowOutput) {
-        // Assert backup was successful.
-        Scanner in = new Scanner(backupnowOutput);
-        while (in.hasNextLine()) {
-            String line = in.nextLine();
-
-            if (line.contains(PACKAGE_UNDER_TEST)) {
-                String result = line.split(":")[1].trim();
-
-                assertEquals(result, "Success");
-            }
-        }
-        in.close();
-    }
-
-    /**
      * Reads the values logged by the app and verifies that they are the same as the ones we saved
      * in {@code mSavedValues}.
      */
@@ -297,46 +243,6 @@
      * Returns the logcat string with the tag {@param className} and clears everything else.
      */
     private String getLogcatForClass(String className) throws DeviceNotAvailableException {
-        return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d",
-                className + ":I", "*:S");
-    }
-
-    // Copied over from BackupQuotaTest
-    private boolean enableBackup(boolean enable) throws Exception {
-        boolean previouslyEnabled;
-        String output = mDevice.executeShellCommand("bmgr enabled");
-        Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$");
-        Matcher matcher = pattern.matcher(output.trim());
-        if (matcher.find()) {
-            previouslyEnabled = "enabled".equals(matcher.group(1));
-        } else {
-            throw new RuntimeException("non-parsable output setting bmgr enabled: " + output);
-        }
-
-        mDevice.executeShellCommand("bmgr enable " + enable);
-        return previouslyEnabled;
-    }
-
-    // Copied over from BackupQuotaTest
-    private String setBackupTransport(String transport) throws Exception {
-        String output = mDevice.executeShellCommand("bmgr transport " + transport);
-        Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$");
-        Matcher matcher = pattern.matcher(output);
-        if (matcher.find()) {
-            return matcher.group(1);
-        } else {
-            throw new RuntimeException("non-parsable output setting bmgr transport: " + output);
-        }
-    }
-
-    // Copied over from BackupQuotaTest
-    private boolean hasBackupTransport(String transport) throws Exception {
-        String output = mDevice.executeShellCommand("bmgr list transports");
-        for (String t : output.split(" ")) {
-            if (transport.equals(t.trim())) {
-                return true;
-            }
-        }
-        return false;
+        return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", className + ":I", "*:S");
     }
 }
diff --git a/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
new file mode 100644
index 0000000..8380184
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.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.cts.backup;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Scanner;
+
+/**
+ * Base class for CTS backup/restore hostside tests
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public abstract class BaseBackupHostSideTest extends CompatibilityHostTestBase {
+
+    /** Value of PackageManager.FEATURE_BACKUP */
+    private static final String FEATURE_BACKUP = "android.software.backup";
+
+    private static final String LOCAL_TRANSPORT =
+            "android/com.android.internal.backup.LocalTransport";
+
+    private boolean mIsBackupSupported;
+    private boolean mWasBackupEnabled;
+    private String mOldTransport;
+
+    @Before
+    public void setUp() throws DeviceNotAvailableException, Exception {
+        mIsBackupSupported = mDevice.hasFeature("feature:" + FEATURE_BACKUP);
+        assumeTrue(mIsBackupSupported);
+        // Enable backup and select local backup transport
+        assertTrue("LocalTransport should be available.", hasBackupTransport(LOCAL_TRANSPORT));
+        mWasBackupEnabled = enableBackup(true);
+        mOldTransport = setBackupTransport(LOCAL_TRANSPORT);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mIsBackupSupported) {
+            setBackupTransport(mOldTransport);
+            enableBackup(mWasBackupEnabled);
+        }
+    }
+
+    /**
+     * Execute shell command "bmgr backupnow <packageName>" and return output from this command.
+     */
+    protected String backupNow(String packageName) throws DeviceNotAvailableException {
+        return mDevice.executeShellCommand("bmgr backupnow " + packageName);
+    }
+
+    /**
+     * Execute shell command "bmgr restore <packageName>" and return output from this command.
+     */
+    protected String restore(String packageName) throws DeviceNotAvailableException {
+        return mDevice.executeShellCommand("bmgr restore " + packageName);
+    }
+
+    /**
+     * Run test <testName> in test <className> found in package <packageName> on the device, and
+     * assert it is successful.
+     */
+    protected void checkDeviceTest(String packageName, String className, String testName)
+            throws DeviceNotAvailableException {
+        boolean result = runDeviceTests(packageName, className, testName);
+        assertTrue("Device test failed: " + testName, result);
+    }
+
+    /**
+     * Parsing the output of "bmgr backupnow" command and checking that the package under test
+     * was backed up successfully.
+     *
+     * Expected format: "Package <packageName> with result: Success"
+     */
+    protected void assertBackupIsSuccessful(String packageName, String backupnowOutput) {
+        // Assert backup was successful.
+        Scanner in = new Scanner(backupnowOutput);
+        boolean success = false;
+        while (in.hasNextLine()) {
+            String line = in.nextLine();
+
+            if (line.contains(packageName)) {
+                String result = line.split(":")[1].trim();
+                if ("Success".equals(result)) {
+                    success = true;
+                }
+            }
+        }
+        in.close();
+        assertTrue(success);
+    }
+
+    /**
+     * Parsing the output of "bmgr restore" command and checking that the package under test
+     * was restored successfully.
+     *
+     * Expected format: "restoreFinished: 0"
+     */
+    protected void assertRestoreIsSuccessful(String restoreOutput) {
+        assertTrue("Restore not successful", restoreOutput.contains("restoreFinished: 0"));
+    }
+
+    // Copied over from BackupQuotaTest
+    private boolean enableBackup(boolean enable) throws Exception {
+        boolean previouslyEnabled;
+        String output = mDevice.executeShellCommand("bmgr enabled");
+        Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$");
+        Matcher matcher = pattern.matcher(output.trim());
+        if (matcher.find()) {
+            previouslyEnabled = "enabled".equals(matcher.group(1));
+        } else {
+            throw new RuntimeException("non-parsable output setting bmgr enabled: " + output);
+        }
+
+        mDevice.executeShellCommand("bmgr enable " + enable);
+        return previouslyEnabled;
+    }
+
+    // Copied over from BackupQuotaTest
+    private String setBackupTransport(String transport) throws Exception {
+        String output = mDevice.executeShellCommand("bmgr transport " + transport);
+        Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$");
+        Matcher matcher = pattern.matcher(output);
+        if (matcher.find()) {
+            return matcher.group(1);
+        } else {
+            throw new RuntimeException("non-parsable output setting bmgr transport: " + output);
+        }
+    }
+
+    // Copied over from BackupQuotaTest
+    private boolean hasBackupTransport(String transport) throws Exception {
+        String output = mDevice.executeShellCommand("bmgr list transports");
+        for (String t : output.split(" ")) {
+            if (transport.equals(t.trim())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/NoBackupFolderHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/NoBackupFolderHostSideTest.java
new file mode 100644
index 0000000..56f8f5c
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/NoBackupFolderHostSideTest.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 android.cts.backup;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test checking that files created by an app are restored successfully after a backup, but that
+ * files put in the folder provided by getNoBackupFilesDir() [files/no_backup] are NOT backed up.
+ *
+ * Invokes device side tests provided by android.cts.backup.fullbackupapp.FullbackupTest.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class NoBackupFolderHostSideTest extends BaseBackupHostSideTest {
+
+    private static final String TESTS_APP_NAME = "android.cts.backup.fullbackupapp";
+    private static final String DEVICE_TEST_CLASS_NAME = TESTS_APP_NAME + ".FullbackupTest";
+
+    @Test
+    public void testNoBackupFolder() throws Exception {
+        // Generate the files that are going to be backed up.
+        checkDeviceTest(TESTS_APP_NAME, DEVICE_TEST_CLASS_NAME, "createFiles");
+
+        // Do a backup
+        String backupnowOutput = backupNow(TESTS_APP_NAME);
+
+        assertBackupIsSuccessful(TESTS_APP_NAME, backupnowOutput);
+
+        // Delete the files
+        checkDeviceTest(TESTS_APP_NAME, DEVICE_TEST_CLASS_NAME, "deleteFilesAfterBackup");
+
+        // Do a restore
+        String restoreOutput = restore(TESTS_APP_NAME);
+
+        assertRestoreIsSuccessful(restoreOutput);
+
+        // Check that the right files were restored
+        checkDeviceTest(TESTS_APP_NAME, DEVICE_TEST_CLASS_NAME, "checkRestoredFiles");
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ScreenCaptureDisabledTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ScreenCaptureDisabledTest.java
index c5b3e96..e556bd9 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ScreenCaptureDisabledTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ScreenCaptureDisabledTest.java
@@ -17,16 +17,18 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
 
 /**
  * Tests for {@link DevicePolicyManager#setScreenCaptureDisabled} and
  * {@link DevicePolicyManager#getScreenCaptureDisabled} APIs.
  */
 public class ScreenCaptureDisabledTest extends BaseDeviceAdminTest {
+    private final int MAX_ATTEMPTS_COUNT = 20;
+    private final int WAIT_IN_MILLISECOND = 500;
 
     private static final String TAG = "ScreenCaptureDisabledTest";
 
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -45,10 +47,24 @@
     }
 
     public void testScreenCaptureImpossible() throws Exception {
+        for (int i = 0; i < MAX_ATTEMPTS_COUNT; i++) {
+            Log.d(TAG, "testScreenCaptureImpossible: " + i + " trials");
+            Thread.sleep(WAIT_IN_MILLISECOND);
+            if (getInstrumentation().getUiAutomation().takeScreenshot() == null) {
+                break;
+            }
+        }
         assertNull(getInstrumentation().getUiAutomation().takeScreenshot());
     }
 
     public void testScreenCapturePossible() throws Exception {
+        for (int i = 0; i < MAX_ATTEMPTS_COUNT; i++) {
+            Log.d(TAG, "testScreenCapturePossible: " + i + " trials");
+            Thread.sleep(WAIT_IN_MILLISECOND);
+            if (getInstrumentation().getUiAutomation().takeScreenshot() != null) {
+                break;
+            }
+        }
         assertNotNull(getInstrumentation().getUiAutomation().takeScreenshot());
     }
 }
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
index 9375e9b..7ed8722 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
@@ -20,6 +20,7 @@
 
 import com.google.common.base.Charsets;
 
+import java.util.Arrays;
 import java.util.Random;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -82,6 +83,23 @@
         super.tearDown();
     }
 
+    protected void screenOff() throws Exception {
+        getDevice().executeShellCommand("dumpsys batterystats enable pretend-screen-off");
+    }
+
+    /**
+     * This will turn the screen on for real, not just disabling pretend-screen-off
+     */
+    protected void turnScreenOnForReal() throws Exception {
+        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        getDevice().executeShellCommand("wm dismiss-keyguard");
+    }
+
+    protected void batteryOnScreenOn() throws Exception {
+        getDevice().executeShellCommand("dumpsys battery unplug");
+        getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
+    }
+
     protected void batteryOnScreenOff() throws Exception {
         getDevice().executeShellCommand("dumpsys battery unplug");
         getDevice().executeShellCommand("dumpsys batterystats enable pretend-screen-off");
@@ -92,6 +110,14 @@
         getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
     }
 
+    private void forceStop() throws Exception {
+        getDevice().executeShellCommand("am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
+    }
+
+    private void resetBatteryStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys batterystats --reset");
+    }
+
     public void testAlarms() throws Exception {
         batteryOnScreenOff();
 
@@ -353,6 +379,118 @@
         batteryOffScreenOn();
     }
 
+    public void testCpuFreqData() throws Exception {
+        batteryOnScreenOff();
+        final long[] actualCpuFreqs = CpuFreqDataHelper.getCpuFreqFromCheckinDump(getDevice());
+        final long[] expectedCpuFreqs = CpuFreqDataHelper.getCpuFreqFromProcFile(getDevice());
+        assertTrue("Unexpected cpu freqs, actual: " + Arrays.toString(actualCpuFreqs)
+                + ", expected: " + Arrays.toString(expectedCpuFreqs),
+                        Arrays.equals(actualCpuFreqs, expectedCpuFreqs));
+        batteryOffScreenOn();
+    }
+
+    public void testUidCpuFreqTimesData() throws Exception {
+        batteryOnScreenOn();
+        // Previous call will only disable the pretend-screen-off. Since we are interested in
+        // checking the uid times while the screen is on, turn it on for real.
+        turnScreenOnForReal();
+        installPackage(DEVICE_SIDE_TEST_APK, true);
+        final int uid = getUid();
+        final long[] initialSnapshot = CpuFreqDataHelper.getUidCpuFreqTimesFromProcFile(
+                getDevice(), uid);
+        forceStop();
+        resetBatteryStats();
+        assertNull(CpuFreqDataHelper.getUidCpuFreqTimesFromCheckinDump(getDevice(), uid));
+
+        // Do some work with screen on
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsAlarmTest", "testAlarms");
+        // Now do some work with screen off
+        screenOff();
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsJobDurationTests",
+                "testJobDuration");
+        forceStop();
+
+        final long[] uidTimesFromDump = CpuFreqDataHelper.getUidCpuFreqTimesFromCheckinDump(
+                getDevice(), uid);
+        final long[] finalSnapshot = CpuFreqDataHelper.getUidCpuFreqTimesFromProcFile(
+                getDevice(), uid);
+        final long[] uidTimesFromProcFile =
+                CpuFreqDataHelper.subtract(finalSnapshot, initialSnapshot);
+        final long[] totalUidTimesFromDump = CpuFreqDataHelper.getTotalCpuTimes(uidTimesFromDump);
+        assertTrue("Unexcepted total uid times, from dump: " + Arrays.toString(uidTimesFromDump)
+                        + ", from procFile: " + Arrays.toString(uidTimesFromProcFile),
+                Arrays.equals(totalUidTimesFromDump, uidTimesFromProcFile));
+        batteryOffScreenOn();
+    }
+
+    public void testUidCpuFreqTimesDataWithOnlyScreenOn() throws Exception {
+        batteryOnScreenOn();
+        // Previous call will only disable the pretend-screen-off. Since we are interested in
+        // checking the uid times while the screen is on, turn it on for real.
+        turnScreenOnForReal();
+        installPackage(DEVICE_SIDE_TEST_APK, true);
+        final int uid = getUid();
+        final long[] initialSnapshot = CpuFreqDataHelper.getUidCpuFreqTimesFromProcFile(
+                getDevice(), uid);
+        forceStop();
+        resetBatteryStats();
+        assertNull(CpuFreqDataHelper.getUidCpuFreqTimesFromCheckinDump(getDevice(), uid));
+
+        // Do some work
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsAlarmTest", "testAlarms");
+        forceStop();
+
+        final long[] uidTimesFromDump = CpuFreqDataHelper.getUidCpuFreqTimesFromCheckinDump(
+                getDevice(), uid);
+        final long[] finalSnapshot = CpuFreqDataHelper.getUidCpuFreqTimesFromProcFile(
+                getDevice(), uid);
+        final long[] uidTimesFromProcFile =
+                CpuFreqDataHelper.subtract(finalSnapshot, initialSnapshot);
+        final long[] totalUidTimesFromDump = CpuFreqDataHelper.getTotalCpuTimes(uidTimesFromDump);
+        final long[] screenOnUidTimesFromDump = CpuFreqDataHelper.getScreenOnCpuTimes(
+                uidTimesFromDump);
+        assertTrue("Unexcepted total uid times, from dump: " + Arrays.toString(uidTimesFromDump)
+                + ", from procFile: " + Arrays.toString(uidTimesFromProcFile),
+                        Arrays.equals(totalUidTimesFromDump, uidTimesFromProcFile));
+        assertTrue("Unexcepted screen-on uid times, from dump: " + Arrays.toString(uidTimesFromDump)
+                + ", from procFile: " + Arrays.toString(uidTimesFromProcFile),
+                        Arrays.equals(screenOnUidTimesFromDump, uidTimesFromProcFile));
+        batteryOffScreenOn();
+    }
+
+    public void testUidCpuFreqTimesDataWithOnlyScreenOff() throws Exception {
+        batteryOnScreenOff();
+        installPackage(DEVICE_SIDE_TEST_APK, true);
+        final int uid = getUid();
+        final long[] initialSnapshot = CpuFreqDataHelper.getUidCpuFreqTimesFromProcFile(
+                getDevice(), uid);
+        forceStop();
+        resetBatteryStats();
+        assertNull(CpuFreqDataHelper.getUidCpuFreqTimesFromCheckinDump(getDevice(), uid));
+
+        // Do some work
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsAlarmTest", "testAlarms");
+        forceStop();
+
+        final long[] uidTimesFromDump = CpuFreqDataHelper.getUidCpuFreqTimesFromCheckinDump(
+                getDevice(), uid);
+        final long[] finalSnapshot = CpuFreqDataHelper.getUidCpuFreqTimesFromProcFile(
+                getDevice(), uid);
+        final long[] uidTimesFromProcFile =
+                CpuFreqDataHelper.subtract(finalSnapshot, initialSnapshot);
+        final long[] totalUidTimesFromDump = CpuFreqDataHelper.getTotalCpuTimes(uidTimesFromDump);
+        final long[] screenOffUidTimesFromDump = CpuFreqDataHelper.getScreenOffCpuTimes(
+                uidTimesFromDump);
+        assertTrue("Unexcepted total uid times, from dump: " + Arrays.toString(uidTimesFromDump)
+                        + ", from procFile: " + Arrays.toString(uidTimesFromProcFile),
+                Arrays.equals(totalUidTimesFromDump, uidTimesFromProcFile));
+        assertTrue("Unexcepted screen-off uid times, from dump: "
+                + Arrays.toString(uidTimesFromDump) + ", from procFile: "
+                + Arrays.toString(uidTimesFromProcFile),
+                        Arrays.equals(screenOffUidTimesFromDump, uidTimesFromProcFile));
+        batteryOffScreenOn();
+    }
+
     /**
      * Tests the total bytes reported for uploading over wifi.
      */
diff --git a/hostsidetests/incident/src/com/android/server/cts/CpuFreqDataHelper.java b/hostsidetests/incident/src/com/android/server/cts/CpuFreqDataHelper.java
new file mode 100644
index 0000000..e9f5f24
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/CpuFreqDataHelper.java
@@ -0,0 +1,152 @@
+/*
+ * 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 static org.junit.Assert.fail;
+
+import com.android.tradefed.device.ITestDevice;
+
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CpuFreqDataHelper {
+
+    static long[] getCpuFreqFromProcFile(ITestDevice device) throws Exception {
+        final String output = getProcFileContents(device);
+        final String line = output.substring(0, output.indexOf('\n'));
+        final String[] freqStr = line.split(" ");
+        int freqCount = freqStr.length - 1;
+        final long[] cpuFreqs = new long[freqCount];
+        for (int i = 0; i < freqCount; ++i) {
+            cpuFreqs[i] = Long.parseLong(freqStr[i + 1]);
+        }
+        return cpuFreqs;
+    }
+
+    static long[] getCpuFreqFromCheckinDump(ITestDevice device) throws Exception {
+        final String dumpsys = getCheckinDump(device);
+        final Pattern pattern = Pattern.compile("0,l,gcf,(.*?)\n");
+        final Matcher matcher = pattern.matcher(dumpsys);
+        if (matcher.find()) {
+            return parseLongs(matcher.group(1).split(","));
+        } else {
+            fail("Could not find cpu freqs in checkin dump");
+            return null;
+        }
+    }
+
+    static long[] getUidCpuFreqTimesFromProcFile(ITestDevice device, int uid) throws Exception {
+        final String output = getProcFileContents(device);
+        final Pattern pattern = Pattern.compile(uid + ": (.*?)\n");
+        final Matcher matcher = pattern.matcher(output);
+        if (matcher.find()) {
+            return parseLongs(matcher.group(1).split(" "), 10);
+        } else {
+            return null;
+        }
+    }
+
+    static long[] getUidCpuFreqTimesFromCheckinDump(ITestDevice device, int uid) throws Exception {
+        final String output = getCheckinDump(device);
+        final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n");
+        final Matcher matcher = pattern.matcher(output);
+        if (matcher.find()) {
+            final String[] uidTimesStr = matcher.group(1).split(",");
+            final int freqCount = Integer.parseInt(uidTimesStr[0]);
+            if (uidTimesStr.length != (freqCount * 2 + 1)) {
+                fail("Malformed data: " + Arrays.toString(uidTimesStr));
+            }
+            final long[] uidTimes = new long[freqCount * 2];
+            for (int i = 0; i < uidTimes.length; ++i) {
+                uidTimes[i] = Long.parseLong(uidTimesStr[i + 1]);
+            }
+            return uidTimes;
+        } else {
+            return null;
+        }
+    }
+
+    // first half will be total cpu times and the rest are screen off cpu times
+    static long[] getScreenOnCpuTimes(long[] cpuTimes) {
+        final int freqCount = cpuTimes.length / 2;
+        final long[] screenOnCpuTimes = new long[freqCount];
+        for (int i = 0; i < freqCount; ++i) {
+            screenOnCpuTimes[i] = cpuTimes[i] - cpuTimes[i + freqCount];
+        }
+        return screenOnCpuTimes;
+    }
+
+    // first half will be total cpu times and the rest are screen off cpu times
+    static long[] getScreenOffCpuTimes(long[] cpuTimes) {
+        final int freqCount = cpuTimes.length / 2;
+        final long[] screenOffCpuTimes = new long[freqCount];
+        for (int i = 0; i < freqCount; ++i) {
+            screenOffCpuTimes[i] = cpuTimes[i + freqCount];
+        }
+        return screenOffCpuTimes;
+    }
+
+    // first half will be total cpu times and the rest are screen off cpu times
+    static long[] getTotalCpuTimes(long[] cpuTimes) {
+        final int freqCount = cpuTimes.length / 2;
+        final long[] totalCpuTimes = new long[freqCount];
+        for (int i = 0; i < freqCount; ++i) {
+            totalCpuTimes[i] = cpuTimes[i];
+        }
+        return totalCpuTimes;
+    }
+
+    static long[] subtract(long[] a, long[] b) {
+        if (b == null) {
+            return a;
+        }
+        final long[] values = new long[a.length];
+        for (int i = 0; i < a.length; ++i) {
+            values[i] = a[i] - b[i];
+        }
+        return values;
+    }
+
+    private static long[] parseLongs(String[] line, long scale) {
+        final long[] longs = new long[line.length];
+        for (int i = 0; i < line.length; ++i) {
+            longs[i] = Long.parseLong(line[i]) * scale;
+        }
+        return longs;
+    }
+
+    private static long[] parseLongs(String[] line) {
+        return parseLongs(line, 1);
+    }
+
+    private static String getProcFileContents(ITestDevice device) throws Exception {
+        final String output = device.executeShellCommand("cat /proc/uid_time_in_state");
+        if (output == null) {
+            fail("proc file /proc/uid_time_in_state is empty");
+        }
+        return output;
+    }
+
+    private static String getCheckinDump(ITestDevice device) throws Exception {
+        final String output = device.executeShellCommand("dumpsys batterystats --checkin");
+        if (output == null) {
+            fail("dumpsys --checking data is empty");
+        }
+        return output;
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/media/bitstreams/Android.mk b/hostsidetests/media/bitstreams/Android.mk
new file mode 100644
index 0000000..b07309c
--- /dev/null
+++ b/hostsidetests/media/bitstreams/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_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, common/src)
+
+LOCAL_JAVA_LIBRARIES := compatibility-host-util cts-tradefed tradefed
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := CtsMediaBitstreamsTestCases
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/media/bitstreams/AndroidTest.xml b/hostsidetests/media/bitstreams/AndroidTest.xml
new file mode 100644
index 0000000..fe06a09
--- /dev/null
+++ b/hostsidetests/media/bitstreams/AndroidTest.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.
+-->
+<configuration description="Config for CTS Sample host test cases">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaBitstreamsDeviceSideTestApp.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaBitstreamsTestCases" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsMediaBitstreamsTestCases.jar" />
+        <option name="runtime-hint" value="6m" />
+    </test>
+</configuration>
diff --git a/hostsidetests/media/bitstreams/app/Android.mk b/hostsidetests/media/bitstreams/app/Android.mk
new file mode 100644
index 0000000..df8e6a4
--- /dev/null
+++ b/hostsidetests/media/bitstreams/app/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)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util media-bitstreams-common-devicesidelib
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsMediaBitstreamsDeviceSideTestApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/media/bitstreams/app/AndroidManifest.xml b/hostsidetests/media/bitstreams/app/AndroidManifest.xml
new file mode 100644
index 0000000..d5565fd
--- /dev/null
+++ b/hostsidetests/media/bitstreams/app/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.media.cts.bitstreams.app">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="android.media.cts.bitstreams.app"
+            android:label="Device-side CTS media bitstreams preparation" />
+</manifest>
diff --git a/hostsidetests/media/bitstreams/app/src/android/media/cts/bitstreams/app/MediaBitstreamsDeviceSideTest.java b/hostsidetests/media/bitstreams/app/src/android/media/cts/bitstreams/app/MediaBitstreamsDeviceSideTest.java
new file mode 100644
index 0000000..d7965b7
--- /dev/null
+++ b/hostsidetests/media/bitstreams/app/src/android/media/cts/bitstreams/app/MediaBitstreamsDeviceSideTest.java
@@ -0,0 +1,379 @@
+/*
+ * 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.media.cts.bitstreams.app;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.bitstreams.MediaBitstreams;
+import android.os.Bundle;
+import android.os.Debug;
+import android.support.test.InstrumentationRegistry;
+import android.util.Xml;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+import com.android.compatibility.common.util.MediaUtils;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.xmlpull.v1.XmlSerializer;
+
+/**
+ * Test class that uses device-side media APIs to determine up to which resolution MediaPreparer
+ * should copy media files for CtsMediaStressTestCases.
+ */
+@RunWith(JUnit4.class)
+public class MediaBitstreamsDeviceSideTest {
+
+    private static final String KEY_SIZE = "size";
+    private static final String UTF_8 = "utf-8";
+    private static final String DYNAMIC_CONFIG = "dynamicConfig";
+    private static final String DYNAMIC_CONFIG_ENTRY = "entry";
+    private static final String DYNAMIC_CONFIG_KEY = "key";
+    private static final String DYNAMIC_CONFIG_VALUE = "value";
+
+    /** Instrumentation status code used to write resolution to metrics */
+    private static final int INST_STATUS_IN_PROGRESS = 2;
+
+    private static File mAppCache = InstrumentationRegistry.getContext().getExternalCacheDir();
+    private static String mDeviceBitstreamsPath = InstrumentationRegistry.getArguments().getString(
+            MediaBitstreams.OPT_DEVICE_BITSTEAMS_PATH,
+            MediaBitstreams.DEFAULT_DEVICE_BITSTEAMS_PATH);
+
+    @BeforeClass
+    public static void setUp() {
+        Bundle args = InstrumentationRegistry.getArguments();
+        String debugStr = args.getString(MediaBitstreams.OPT_DEBUG_TARGET_DEVICE, "false");
+        boolean debug = Boolean.parseBoolean(debugStr);
+        if (debug && !Debug.isDebuggerConnected()) {
+            Debug.waitForDebugger();
+        }
+    }
+
+    static interface ReportCallback {
+        void run(OutputStream out) throws Exception;
+    }
+
+    static class GenerateBitstreamsFormatsXml implements ReportCallback {
+        @Override
+        public void run(OutputStream out) throws Exception {
+
+            String[] keys = new String[] {
+                    MediaFormat.KEY_WIDTH,
+                    MediaFormat.KEY_HEIGHT,
+                    MediaFormat.KEY_FRAME_RATE,
+                    MediaFormat.KEY_PROFILE,
+                    MediaFormat.KEY_LEVEL,
+                    MediaFormat.KEY_BIT_RATE};
+
+            XmlSerializer formats = Xml.newSerializer();
+            formats.setOutput(out, UTF_8);
+            formats.startDocument(UTF_8, true);
+            formats.startTag(null, DYNAMIC_CONFIG);
+
+            DynamicConfigDeviceSide config = new DynamicConfigDeviceSide(MediaBitstreams.K_MODULE);
+            for (String path : config.keySet()) {
+
+                formats.startTag(null, DYNAMIC_CONFIG_ENTRY);
+                formats.attribute(null, DYNAMIC_CONFIG_KEY, path);
+                formats.startTag(null, DYNAMIC_CONFIG_VALUE);
+
+                String formatStr = config.getValue(path);
+                if (formatStr != null && !formatStr.isEmpty()) {
+                    formats.text(formatStr);
+                } else {
+                    File media = new File(mDeviceBitstreamsPath, path);
+                    String fullPath = media.getPath();
+                    MediaFormat format = MediaUtils.getTrackFormatForPath(null, fullPath, "video");
+                    StringBuilder formatStringBuilder = new StringBuilder(MediaFormat.KEY_MIME);
+                    formatStringBuilder.append('=').append(format.getString(MediaFormat.KEY_MIME));
+                    formatStringBuilder.append(',').append(KEY_SIZE)
+                            .append('=').append(media.length());
+                    for (String key : keys) {
+                        formatStringBuilder.append(',').append(key).append('=');
+                        if (format.containsKey(key)) {
+                            formatStringBuilder.append(format.getInteger(key));
+                        }
+                    }
+                    formats.text(formatStringBuilder.toString());
+                }
+
+                formats.endTag(null, DYNAMIC_CONFIG_VALUE);
+                formats.endTag(null, DYNAMIC_CONFIG_ENTRY);
+
+            }
+
+            formats.endTag(null, DYNAMIC_CONFIG);
+            formats.endDocument();
+
+        }
+    }
+
+    static class GenerateSupportedBitstreamsFormatsTxt implements ReportCallback {
+
+        @Override
+        public void run(OutputStream out) throws Exception {
+
+            PrintStream ps = new PrintStream(out);
+            Bundle args = InstrumentationRegistry.getArguments();
+            String prefix = args.getString(MediaBitstreams.OPT_BITSTREAMS_PREFIX, "");
+            DynamicConfigDeviceSide config = new DynamicConfigDeviceSide(MediaBitstreams.K_MODULE);
+
+            for (String path : config.keySet()) {
+
+                if (!path.startsWith(prefix)) {
+                    continue;
+                }
+
+                String formatStr = config.getValue(path);
+                if (formatStr == null || formatStr.isEmpty()) {
+                    continue;
+                }
+
+                MediaFormat format = parseTrackFormat(formatStr);
+                if (!MediaUtils.checkDecoderForFormat(format)) {
+                    continue;
+                }
+
+                ps.println(path);
+            }
+            ps.flush();
+        }
+    }
+
+    static class TestBitstreamsConformance implements ReportCallback {
+
+        ExecutorService mExecutorService;
+
+        private SharedPreferences getSettings() {
+            Context ctx = InstrumentationRegistry.getContext();
+            SharedPreferences settings = ctx.getSharedPreferences(MediaBitstreams.K_MODULE, 0);
+            return settings;
+        }
+
+        private void setup() {
+            Bundle args = InstrumentationRegistry.getArguments();
+            String lastCrash = args.getString(MediaBitstreams.OPT_LAST_CRASH);
+            if (lastCrash != null) {
+                SharedPreferences settings = getSettings();
+                int n = settings.getInt(lastCrash, 0);
+                Editor editor = settings.edit();
+                editor.putInt(lastCrash, n + 1);
+                editor.commit();
+            }
+        }
+
+        @Override
+        public void run(OutputStream out) throws Exception {
+            setup();
+            mExecutorService = Executors.newFixedThreadPool(3);
+            try (
+                Scanner sc = new Scanner(
+                        new File(mDeviceBitstreamsPath, MediaBitstreams.K_BITSTREAMS_LIST_TXT));
+                PrintStream ps = new PrintStream(out, true)
+            ) {
+                while (sc.hasNextLine()) {
+                    verifyBitstream(ps, sc.nextLine());
+                }
+            } finally {
+                mExecutorService.shutdown();
+            }
+        }
+
+        private List<String> getDecodersForPath(String path) throws IOException {
+            List<String> decoders = new ArrayList<>();
+            MediaExtractor ex = new MediaExtractor();
+            try {
+                ex.setDataSource(path);
+                MediaFormat format = ex.getTrackFormat(0);
+                boolean[] vendors = new boolean[] {false, true};
+                for (boolean v : vendors) {
+                    for (String name : MediaUtils.getDecoderNames(v, format)) {
+                        decoders.add(name);
+                    }
+                }
+            } finally {
+                ex.release();
+            }
+            return decoders;
+        }
+
+        private List<String> getFrameChecksumsForPath(String path) throws IOException {
+            String md5Path = MediaBitstreams.getMd5Path(path);
+            List<String> frameMD5Sums = Files.readAllLines(
+                    new File(mDeviceBitstreamsPath, md5Path).toPath());
+            for (int i = 0; i < frameMD5Sums.size(); i++) {
+                String line = frameMD5Sums.get(i);
+                frameMD5Sums.set(i, line.split(" ")[0]);
+            }
+            return frameMD5Sums;
+        }
+
+        private void verifyBitstream(PrintStream ps, String relativePath) {
+            ps.println(relativePath);
+
+            List<String> decoders = new ArrayList<>();
+            List<String> frameChecksums = new ArrayList<>();
+            SharedPreferences settings = getSettings();
+            String fullPath = new File(mDeviceBitstreamsPath, relativePath).toString();
+            try {
+                String lastCrash = MediaBitstreams.generateCrashSignature(relativePath, "");
+                if (settings.getInt(lastCrash, 0) >= 3) {
+                    ps.println(MediaBitstreams.K_NATIVE_CRASH);
+                    return;
+                }
+                decoders = getDecodersForPath(fullPath);
+                frameChecksums = getFrameChecksumsForPath(relativePath);
+                ps.println(false);
+            } catch (Exception e) {
+                ps.println(true);
+                ps.println(e.toString());
+                return;
+            }
+
+            ps.println(decoders.size());
+            for (String name : decoders) {
+                ps.println(name);
+                String lastCrash = MediaBitstreams.generateCrashSignature(relativePath, name);
+                if (settings.getInt(lastCrash, 0) >= 3) {
+                    ps.println(MediaBitstreams.K_NATIVE_CRASH);
+                } else {
+                    ps.println(verifyBitstream(fullPath, name, frameChecksums));
+                }
+            }
+
+        }
+
+        private String verifyBitstream(String path, String name, List<String> frameChecksums)  {
+            MediaExtractor ex = new MediaExtractor();
+            MediaCodec d = null;
+            try {
+                MediaCodec decoder = d = MediaCodec.createByCodecName(name);
+                ex.setDataSource(path);
+                ex.selectTrack(0);
+                ex.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                Future<Boolean> conform = mExecutorService.submit(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        return MediaUtils.verifyDecoder(decoder, ex, frameChecksums);
+                    }
+                });
+                return conform.get(15, TimeUnit.SECONDS).toString();
+            } catch (Exception e) {
+                return e.toString();
+            } finally {
+                ex.release();
+                if (d != null) {
+                    d.release();
+                }
+            }
+        }
+
+    }
+
+    private void generateReportFile(String suffix, String reportKey, ReportCallback callback)
+            throws IOException, FileNotFoundException, Exception {
+
+        OutputStream out = new ByteArrayOutputStream(0);
+
+        try {
+
+            File tmpf = File.createTempFile(getClass().getSimpleName(), suffix, mAppCache);
+            Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+            Bundle bundle = new Bundle();
+            bundle.putString(MediaBitstreams.KEY_APP_CACHE_DIR, mAppCache.getCanonicalPath());
+            bundle.putString(reportKey, tmpf.getCanonicalPath());
+            inst.sendStatus(INST_STATUS_IN_PROGRESS, bundle);
+
+            out = new FileOutputStream(tmpf);
+            callback.run(out);
+            out.flush();
+
+        } finally {
+
+            out.close();
+
+        }
+    }
+
+    @Test
+    public void testGetBitstreamsFormats() throws Exception {
+        generateReportFile(".xml",
+                MediaBitstreams.KEY_BITSTREAMS_FORMATS_XML,
+                new GenerateBitstreamsFormatsXml());
+    }
+
+    @Test
+    public void testGetSupportedBitstreams() throws Exception {
+        generateReportFile(".txt",
+                MediaBitstreams.KEY_SUPPORTED_BITSTREAMS_TXT,
+                new GenerateSupportedBitstreamsFormatsTxt());
+    }
+
+    @Test
+    public void testBitstreamsConformance() throws Exception {
+        generateReportFile(".txt",
+                MediaBitstreams.KEY_BITSTREAMS_VALIDATION_TXT,
+                new TestBitstreamsConformance());
+    }
+
+    /**
+     * Converts a single media track format string into a MediaFormat object
+     *
+     * @param trackFormatString a string representation of the format of one media track
+     * @return a MediaFormat
+     */
+    private static MediaFormat parseTrackFormat(String trackFormatString) {
+        MediaFormat format = new MediaFormat();
+        format.setString(MediaFormat.KEY_MIME, "");
+        for (String entry : trackFormatString.split(",")) {
+            String[] kv = entry.split("=");
+            if (kv.length < 2 || kv[1].isEmpty()) {
+                continue;
+            }
+            String k = kv[0];
+            String v = kv[1];
+            try {
+                format.setInteger(k, Integer.parseInt(v));
+            } catch (NumberFormatException e) {
+                format.setString(k, v);
+            }
+        }
+        return format;
+    }
+}
diff --git a/hostsidetests/media/bitstreams/common/Android.mk b/hostsidetests/media/bitstreams/common/Android.mk
new file mode 100644
index 0000000..1691257
--- /dev/null
+++ b/hostsidetests/media/bitstreams/common/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+###############################################################################
+# Build the common library for use device-side
+###############################################################################
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := media-bitstreams-common-devicesidelib
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.java b/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.java
new file mode 100644
index 0000000..2266254
--- /dev/null
+++ b/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.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 android.media.cts.bitstreams;
+
+/**
+ * This class provides constants and utilities shared between the host-side and device-side test
+ * components.
+ */
+public class MediaBitstreams {
+
+    /* options */
+    public static final String OPT_HOST_BITSTEAMS_PATH = "host-bitsteams-path";
+    public static final String OPT_DEVICE_BITSTEAMS_PATH = "device-bitsteams-path";
+    public static final String OPT_DOWNLOAD_BITSTREAMS = "download-bitstreams";
+    public static final String OPT_DEBUG_TARGET_DEVICE = "debug-target-device";
+    public static final String OPT_BITSTREAMS_TO_TEST_TXT = "bitstreams-to-test-txt";
+    public static final String OPT_UTILIZATION_RATE = "utilization-rate";
+    public static final String OPT_NUM_BATCHES = "num-batches";
+    public static final String OPT_LAST_CRASH = "last-crash";
+    public static final String OPT_BITSTREAMS_PREFIX = "prefix";
+
+    /* defaults */
+    public static final String DEFAULT_HOST_BITSTREAMS_PATH = "TestVectorsIttiam";
+    public static final String DEFAULT_DEVICE_BITSTEAMS_PATH = "/data/local/tmp/TestVectorsIttiam";
+
+    /* metric keys */
+    public static final String KEY_BITSTREAMS_FORMATS_XML = "bitstreams-formats-xml";
+    public static final String KEY_SUPPORTED_BITSTREAMS_TXT = "supported-bitstreams-txt";
+    public static final String KEY_BITSTREAMS_VALIDATION_TXT = "bitstreams-validation-txt";
+    public static final String KEY_APP_CACHE_DIR = "app-cache-dir";
+    public static final String KEY_ERR_MSG = "err-msg";
+
+    /* constants */
+    public static final String K_MODULE = "CtsMediaBitstreamsTestCases";
+    public static final String K_BITSTREAMS_LIST_TXT = "bitstreamsFile.txt";
+    public static final String K_TEST_GET_SUPPORTED_BITSTREAMS = "testGetSupportedBitstreams";
+    public static final String K_NATIVE_CRASH = "native crash";
+
+    /* utilities */
+    /**
+     * @param bitstreamPath path of individual bitstream relative to bitstreams root,
+     * e.g. {@code h264/../../../../*.mp4}
+     * @return checksum file path for {@code bitstreamPath}, e.g. {@code h264/../../../../*_md5}.
+     */
+    public static String getMd5Path(String bitstreamPath) {
+        String base = bitstreamPath.split("\\.")[0];
+        String codec = bitstreamPath.split("/", 2)[0];
+        String md5Path = String.format("%s_%s_md5", base, codec);
+        return md5Path;
+    }
+
+    /**
+     * @param path relative bitstream path, e.g. {@code h264/../../../../*.mp4}
+     * @param name codec name, e.g. {@code OMX.google.h264.decoder}
+     * @return crash signature for a crashed device decoding session,
+     * in the form of {@code <bitstream path>:<codec name>}
+     */
+    public static String generateCrashSignature(String path, String name) {
+        return String.format("%s:%s", path, name);
+    }
+
+
+}
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java
new file mode 100644
index 0000000..5f5e4cd
--- /dev/null
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java
@@ -0,0 +1,690 @@
+/*
+ * 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.media.cts.bitstreams;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.MetricsReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.util.FileUtil;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that verifies video bitstreams decode pixel perfectly
+ */
+@OptionClass(alias="media-bitstreams-test")
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MediaBitstreamsTest implements IDeviceTest, IBuildReceiver, IAbiReceiver {
+
+    @Option(name = MediaBitstreams.OPT_HOST_BITSTEAMS_PATH,
+            description = "Absolute path of Ittiam bitstreams (host)",
+            mandatory = true)
+    private File mHostBitstreamsPath = new File(MediaBitstreams.DEFAULT_HOST_BITSTREAMS_PATH);
+
+    @Option(name = MediaBitstreams.OPT_DEVICE_BITSTEAMS_PATH,
+            description = "Absolute path of Ittiam bitstreams (device)")
+    private String mDeviceBitstreamsPath = MediaBitstreams.DEFAULT_DEVICE_BITSTEAMS_PATH;
+
+    @Option(name = MediaBitstreams.OPT_DOWNLOAD_BITSTREAMS,
+            description = "Whether to download the bitstreams files")
+    private boolean mDownloadBitstreams = false;
+
+    @Option(name = MediaBitstreams.OPT_UTILIZATION_RATE,
+            description = "Percentage of external storage space used for test")
+    private int mUtilizationRate = 80;
+
+    @Option(name = MediaBitstreams.OPT_NUM_BATCHES,
+            description = "Number of batches to test;"
+                    + " each batch uses external storage up to utilization rate")
+    private int mNumBatches = Integer.MAX_VALUE;
+
+    @Option(name = MediaBitstreams.OPT_DEBUG_TARGET_DEVICE,
+            description = "Whether to debug target device under test")
+    private boolean mDebugTargetDevice = false;
+
+    @Option(name = MediaBitstreams.OPT_BITSTREAMS_PREFIX,
+            description = "Only test bitstreams in this sub-directory")
+    private String mPrefix = "";
+
+    /**
+     * A helper to access resources in the build.
+     */
+    private CompatibilityBuildHelper mBuildHelper;
+
+    private IAbi mAbi;
+    private ITestDevice mDevice;
+
+    private MediaBitstreamsTest(String prefix) {
+        mPrefix = prefix;
+    }
+
+    public MediaBitstreamsTest() {
+        this("");
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        // Get the build, this is used to access the APK.
+        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+    }
+
+    @Override
+    public void setAbi(IAbi abi) {
+        mAbi = abi;
+    }
+
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    /*
+     * Returns true if all necessary media files exist on the device, and false otherwise.
+     *
+     * This method is exposed for unit testing.
+     */
+    private boolean bitstreamsExistOnDevice(ITestDevice device)
+            throws DeviceNotAvailableException {
+        return device.doesFileExist(mDeviceBitstreamsPath)
+                && device.isDirectory(mDeviceBitstreamsPath);
+    }
+
+    private String getCurrentMethod() {
+        return Thread.currentThread().getStackTrace()[2].getMethodName();
+    }
+
+    Map<String, String> getArgs() {
+        Map<String, String> args = new HashMap<>();
+        args.put(MediaBitstreams.OPT_DEBUG_TARGET_DEVICE, Boolean.toString(mDebugTargetDevice));
+        args.put(MediaBitstreams.OPT_DEVICE_BITSTEAMS_PATH, mDeviceBitstreamsPath);
+        return args;
+    }
+
+    private class ProcessBitstreamsFormats extends ReportProcessor {
+
+        @Override
+        void setUp(ITestDevice device) throws DeviceNotAvailableException {
+            if (mDownloadBitstreams || !bitstreamsExistOnDevice(device)) {
+                device.pushDir(mHostBitstreamsPath, mDeviceBitstreamsPath);
+            }
+        }
+
+        @Override
+        Map<String, String> getArgs() {
+            return MediaBitstreamsTest.this.getArgs();
+        }
+
+        @Override
+        void process(ITestDevice device, String reportPath)
+                throws DeviceNotAvailableException, IOException {
+            File testDir = mBuildHelper.getTestsDir();
+            File dynamicConfigFile = new File(testDir, MediaBitstreams.K_MODULE + ".dynamic");
+            device.pullFile(reportPath, dynamicConfigFile);
+            CLog.i("Pulled bitstreams formats to %s", dynamicConfigFile.getPath());
+        }
+
+    }
+
+    private class ProcessBitstreamsValidation extends ReportProcessor {
+
+        Set<String> mBitstreams;
+        Deque<String> mProcessedBitstreams = new ArrayDeque<>();
+        private final String mMethodName;
+        private final String mBitstreamsListTxt = new File(
+                mDeviceBitstreamsPath,
+                MediaBitstreams.K_BITSTREAMS_LIST_TXT).toString();
+        private String mLastCrash;
+
+        ProcessBitstreamsValidation(Set<String> bitstreams, String methodName) {
+            mBitstreams = bitstreams;
+            mMethodName = methodName;
+        }
+
+        private String getBitstreamsListString() {
+            OutputStream baos = new ByteArrayOutputStream();
+            PrintStream ps = new PrintStream(baos, true);
+            try {
+                for (String b : mBitstreams) {
+                    ps.println(b);
+                }
+                return baos.toString();
+            } finally {
+                ps.close();
+            }
+        }
+
+        private void pushBitstreams(ITestDevice device)
+                throws IOException, DeviceNotAvailableException {
+            File tmp = null;
+            try {
+                CLog.i("Pushing %d bitstream(s) from %s to %s",
+                        mBitstreams.size(),
+                        mHostBitstreamsPath,
+                        mDeviceBitstreamsPath);
+                tmp = Files.createTempDirectory(null).toFile();
+                for (String b : mBitstreams) {
+                    String m = MediaBitstreams.getMd5Path(b);
+                    for (String f : new String[] {m, b}) {
+                        File tmpf = new File(tmp, f);
+                        new File(tmpf.getParent()).mkdirs();
+                        FileUtil.copyFile(new File(mHostBitstreamsPath, f), tmpf);
+                    }
+                }
+                device.executeShellCommand(String.format("rm -rf %s", mDeviceBitstreamsPath));
+                device.pushDir(tmp, mDeviceBitstreamsPath);
+                device.pushString(getBitstreamsListString(), mBitstreamsListTxt);
+            } finally {
+                FileUtil.recursiveDelete(tmp);
+            }
+        }
+
+        @Override
+        void setUp(ITestDevice device) throws DeviceNotAvailableException, IOException {
+            pushBitstreams(device);
+        }
+
+        @Override
+        Map<String, String> getArgs() {
+            Map<String, String> args = MediaBitstreamsTest.this.getArgs();
+            if (mLastCrash != null) {
+                args.put(MediaBitstreams.OPT_LAST_CRASH, mLastCrash);
+            }
+            return args;
+        }
+
+        private void parse(ITestDevice device, String reportPath)
+                throws DeviceNotAvailableException {
+            String[] lines = getReportLines(device, reportPath);
+            mProcessedBitstreams.clear();
+            for (int i = 0; i < lines.length;) {
+
+                String path = lines[i++];
+                mProcessedBitstreams.add(path);
+                String className = MediaBitstreamsTest.class.getCanonicalName();
+                MetricsReportLog report = new MetricsReportLog(
+                        mBuildHelper.getBuildInfo(), mAbi.getName(),
+                        String.format("%s#%s", className, mMethodName),
+                        getClass().getSimpleName(), path);
+
+                boolean failedEarly;
+                String errMsg;
+                if (i < lines.length) {
+                    failedEarly = Boolean.parseBoolean(lines[i++]);
+                    errMsg = failedEarly ? lines[i++] : "";
+                } else {
+                    failedEarly = true;
+                    errMsg = MediaBitstreams.K_NATIVE_CRASH;
+                    mLastCrash = MediaBitstreams.generateCrashSignature(path, "");
+                    mProcessedBitstreams.removeLast();
+                }
+                if (failedEarly) {
+                    String keyErrMsg = MediaBitstreams.KEY_ERR_MSG;
+                    report.addValue(keyErrMsg, errMsg, ResultType.NEUTRAL, ResultUnit.NONE);
+                    report.submit();
+                    continue;
+                }
+
+                int n = Integer.parseInt(lines[i++]);
+                for (int j = 0; j < n && i < lines.length; j++) {
+                    String name = lines[i++];
+                    String result;
+                    if (i < lines.length) {
+                        result = lines[i++];
+                    } else {
+                        result = MediaBitstreams.K_NATIVE_CRASH;
+                        mLastCrash = MediaBitstreams.generateCrashSignature(path, name);
+                        mProcessedBitstreams.removeLast();
+                    }
+                    report.addValue(name, result, ResultType.NEUTRAL, ResultUnit.NONE);
+                }
+                report.submit();
+
+            }
+        }
+
+        @Override
+        void process(ITestDevice device, String reportPath)
+                throws DeviceNotAvailableException, IOException {
+            parse(device, reportPath);
+        }
+
+        @Override
+        boolean recover(ITestDevice device, String reportPath)
+                throws DeviceNotAvailableException, IOException {
+            try {
+                parse(device, reportPath);
+                mBitstreams.removeAll(mProcessedBitstreams);
+                device.pushString(getBitstreamsListString(), mBitstreamsListTxt);
+                return true;
+            } catch (RuntimeException e) {
+                CLog.e("Error parsing report; saving report to %s", device.pullFile(reportPath));
+                CLog.e(e);
+                return false;
+            }
+        }
+
+    }
+
+    @Ignore
+    @Test
+    public void testGetBitstreamsFormats() throws DeviceNotAvailableException, IOException {
+        ReportProcessor processor = new ProcessBitstreamsFormats();
+        processor.processDeviceReport(
+                getDevice(),
+                mBuildHelper.getTestsDir(),
+                getCurrentMethod(), MediaBitstreams.KEY_BITSTREAMS_FORMATS_XML);
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpBitrate() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/bitrate");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpLevels() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/levels");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpParamsCrowd_640x360p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_640x360p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpParamsCrowd_854x480p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_854x480p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpParamsCrowd_1280x720p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_1280x720p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpParamsCrowd_1920x1080p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_1920x1080p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpParamsCrowd_3840x2160p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_3840x2160p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpResolutions() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/resolutions");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpSlicesCrowd_640x360p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_640x360p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpSlicesCrowd_854x480p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_854x480p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpSlicesCrowd_1280x720p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_1280x720p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpSlicesCrowd_1920x1080p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_1920x1080p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitBpSlicesCrowd_3840x2160p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_3840x2160p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpBitrate() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/bitrate");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpGopCrowd_640x360p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_640x360p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpGopCrowd_854x480p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_854x480p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpGopCrowd_1280x720p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_1280x720p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpGopCrowd_1920x1080p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_1920x1080p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpGopCrowd_3840x2160p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_3840x2160p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpLevels() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/levels");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpParamsCrowd_640x360p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_640x360p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpParamsCrowd_854x480p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_854x480p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpParamsCrowd_1280x720p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_1280x720p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpParamsCrowd_1920x1080p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_1920x1080p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpParamsCrowd_3840x2160p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_3840x2160p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpResolutions() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/resolutions");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpSlicesCrowd_640x360p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_640x360p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpSlicesCrowd_854x480p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_854x480p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpSlicesCrowd_1280x720p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_1280x720p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpSlicesCrowd_1920x1080p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_1920x1080p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitMpSlicesCrowd_3840x2160p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_3840x2160p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpBitrate() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/bitrate");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpGopCrowd_640x360p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_640x360p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpGopCrowd_854x480p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_854x480p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpGopCrowd_1280x720p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_1280x720p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpGopCrowd_1920x1080p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_1920x1080p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpGopCrowd_3840x2160p50() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_3840x2160p50");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpLevels() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/levels");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpParamsCrowd_640x360p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_640x360p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpParamsCrowd_854x480p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_854x480p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpParamsCrowd_1280x720p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_1280x720p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpParamsCrowd_1920x1080p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_1920x1080p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpParamsCrowd_3840x2160p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_3840x2160p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpResolutions() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/resolutions");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpScalingmatrixCrowd_640x360p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_640x360p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpScalingmatrixCrowd_854x480p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_854x480p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpScalingmatrixCrowd_1280x720p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_1280x720p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpScalingmatrixCrowd_1920x1080p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_1920x1080p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpScalingmatrixCrowd_3840x2160p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_3840x2160p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpSlicesCrowd_640x360p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_640x360p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpSlicesCrowd_854x480p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_854x480p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpSlicesCrowd_1280x720p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_1280x720p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpSlicesCrowd_1920x1080p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_1920x1080p50f32");
+    }
+
+    @Test
+    public void testH264Yuv420_8bitHpSlicesCrowd_3840x2160p50f32() throws Exception {
+        testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_3840x2160p50f32");
+    }
+
+    @Test
+    public void testVp8Yuv420_8bitBitrate() throws Exception {
+        testBitstreamsConformance("vp8/yuv420/8bit/bitrate");
+    }
+
+    @Test
+    public void testVp8Yuv420_8bitParamsCrowd_640x360p50f32() throws Exception {
+        testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_640x360p50f32");
+    }
+
+    @Test
+    public void testVp8Yuv420_8bitParamsCrowd_854x480p50f32() throws Exception {
+        testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_854x480p50f32");
+    }
+
+    @Test
+    public void testVp8Yuv420_8bitParamsCrowd_1280x720p50f32() throws Exception {
+        testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_1280x720p50f32");
+    }
+
+    @Test
+    public void testVp8Yuv420_8bitParamsCrowd_1920x1080p50f32() throws Exception {
+        testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_1920x1080p50f32");
+    }
+
+    @Test
+    public void testVp8Yuv420_8bitParamsCrowd_3840x2160p50f32() throws Exception {
+        testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_3840x2160p50f32");
+    }
+
+    @Test
+    public void testVp8Yuv420_8bitResolution() throws Exception {
+        testBitstreamsConformance("vp8/yuv420/8bit/resolution");
+    }
+
+    @Ignore
+    @Test
+    public void testBitstreamsConformance()
+            throws DeviceNotAvailableException, IOException {
+        testBitstreamsConformance(mPrefix);
+    }
+
+    private void testBitstreamsConformance(String prefix)
+            throws DeviceNotAvailableException, IOException {
+
+        ITestDevice device = getDevice();
+        SupportedBitstreamsProcessor preparer;
+        preparer = new SupportedBitstreamsProcessor(prefix, mDebugTargetDevice);
+        preparer.processDeviceReport(
+                device,
+                mBuildHelper.getTestsDir(),
+                MediaBitstreams.K_TEST_GET_SUPPORTED_BITSTREAMS,
+                MediaBitstreams.KEY_SUPPORTED_BITSTREAMS_TXT);
+        Set<String> supportedBitstreams = preparer.getSupportedBitstreams();
+        CLog.i("%d supported bitstreams under %s", supportedBitstreams.size(), prefix);
+
+        int n = 0;
+        long size = 0;
+        long limit = device.getExternalStoreFreeSpace() * mUtilizationRate * 1024 / 100;
+
+        String currentMethod = getCurrentMethod();
+        Set<String> bitstreams = new LinkedHashSet<>();
+        Iterator<String> iter = supportedBitstreams.iterator();
+
+        for (int i = 0; i < supportedBitstreams.size(); i++) {
+
+            if (n >= mNumBatches) {
+                break;
+            }
+
+            String bitstreamPath = iter.next();
+            File bitstreamFile = new File(mHostBitstreamsPath, bitstreamPath);
+            String md5Path = MediaBitstreams.getMd5Path(bitstreamPath);
+            File md5File = new File(mHostBitstreamsPath, md5Path);
+
+            if (md5File.exists() && bitstreamFile.exists()) {
+                size += md5File.length();
+                size += bitstreamFile.length();
+                bitstreams.add(bitstreamPath);
+            }
+
+            if (size > limit || i + 1 == supportedBitstreams.size()) {
+                ReportProcessor processor;
+                processor = new ProcessBitstreamsValidation(bitstreams, currentMethod);
+                processor.processDeviceReport(
+                        device,
+                        mBuildHelper.getTestsDir(),
+                        currentMethod, MediaBitstreams.KEY_BITSTREAMS_VALIDATION_TXT);
+                bitstreams.clear();
+                size = 0;
+                n++;
+            }
+
+        }
+
+    }
+
+
+}
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
new file mode 100644
index 0000000..8c41347
--- /dev/null
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
@@ -0,0 +1,177 @@
+/*
+ * 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.media.cts.bitstreams;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.AndroidJUnitTest;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A {@link ReportProcessor} installs {@code CtsMediaBitstreamsDeviceSideTestApp.apk}
+ * onto a test device, invokes a report-generating test, then pulls and processes
+ * the generated report.
+ */
+abstract class ReportProcessor {
+
+    private final Map<String, String> mMetrics = new HashMap<>();
+    private String mFailureStackTrace = null;
+
+    private static final String APP_APK = "CtsMediaBitstreamsDeviceSideTestApp.apk";
+    private static final String APP_CLS_NAME = "MediaBitstreamsDeviceSideTest";
+    private static final String APP_PKG_NAME = "android.media.cts.bitstreams.app";
+
+    /**
+     * Setup {@code device} before test.
+     *
+     * @param device device under test
+     * @throws DeviceNotAvailableException
+     * @throws IOException
+     */
+    void setUp(ITestDevice device) throws DeviceNotAvailableException, IOException {}
+
+    Map<String, String> getArgs() {
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Process test report.
+     *
+     * @param device device under test
+     * @param reportPath path to test report on {@code device}
+     * @throws DeviceNotAvailableException
+     * @throws IOException
+     */
+    void process(ITestDevice device, String reportPath)
+            throws DeviceNotAvailableException, IOException {}
+
+    /**
+     * Attempt to recover from a crash during test on {@code device}.
+     *
+     * @param device device under test
+     * @param reportPath path to test report on {@code device}
+     * @throws DeviceNotAvailableException
+     * @throws IOException
+     * @return true if successfully recovered from test crash, false otherwise
+     */
+    boolean recover(ITestDevice device, String reportPath)
+            throws DeviceNotAvailableException, IOException {
+        return false;
+    }
+
+    /**
+     * Cleanup {@code device} after test
+     * @param device device under test
+     * @param reportPath path to test report on {@code device}
+     */
+    void cleanup(ITestDevice device, String reportPath) {
+        try {
+            device.executeShellCommand(String.format("rm %s", reportPath));
+        } catch (DeviceNotAvailableException e) {
+            CLog.e(e);
+        }
+    }
+
+    /**
+     * @param device device under test
+     * @param reportPath path to test report on {@code device}
+     * @return array of lines in report, sans newline
+     * @throws DeviceNotAvailableException
+     */
+    static String[] getReportLines(ITestDevice device, String reportPath)
+            throws DeviceNotAvailableException {
+        String cat = String.format("cat %s", reportPath);
+        String output = device.executeShellCommand(cat);
+        return output.isEmpty() ? new String[0] : output.split("\n");
+    }
+
+    /* Special listener for setting MediaPreparer instance variable values */
+    private class MediaBitstreamsListener implements ITestInvocationListener {
+
+        @Override
+        public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+            mMetrics.putAll(metrics);
+        }
+
+        @Override
+        public void testFailed(TestIdentifier test, String trace) {
+            mFailureStackTrace = trace;
+        }
+
+    }
+
+    private boolean runDeviceTest(
+            ITestDevice device, File testDir, String method, String reportKey,
+            int testTimeout, long shellTimeout)
+            throws DeviceNotAvailableException {
+
+        File apkFile = new File(testDir, APP_APK);
+        device.installPackage(apkFile, true, true);
+
+        String fullTestName = String.format("%s.%s#%s", APP_PKG_NAME, APP_CLS_NAME, method);
+        AndroidJUnitTest instrTest = new AndroidJUnitTest();
+        instrTest.setDevice(device);
+        instrTest.setPackageName(APP_PKG_NAME);
+        instrTest.addIncludeFilter(fullTestName);
+        instrTest.setTestTimeout(testTimeout);
+        instrTest.setShellTimeout(shellTimeout);
+        for (Entry<String, String> e : getArgs().entrySet()) {
+            instrTest.addInstrumentationArg(e.getKey(), e.getValue());
+        }
+        instrTest.run(new MediaBitstreamsListener());
+
+        return checkFile(reportKey);
+
+    }
+
+    private boolean checkFile(String reportKey) {
+        if (mFailureStackTrace != null) {
+            CLog.w("Retrieving bitstreams formats failed with trace:\n%s", mFailureStackTrace);
+            mFailureStackTrace = null;
+            return false;
+        } else if (!mMetrics.containsKey(reportKey)) {
+            CLog.w("Failed to generate file key=%s on device", reportKey);
+            return false;
+        }
+        return true;
+    }
+
+    void processDeviceReport(
+            ITestDevice device, File testDir, String method, String reportKey)
+            throws DeviceNotAvailableException, IOException {
+        try {
+            setUp(device);
+            while (!runDeviceTest(device, testDir, method, reportKey, 0, 0)) {
+                if (!recover(device, mMetrics.get(reportKey))) {
+                    return;
+                }
+            }
+            process(device, mMetrics.get(reportKey));
+        } finally {
+            cleanup(device, mMetrics.get(reportKey));
+        }
+    }
+
+}
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/SupportedBitstreamsProcessor.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/SupportedBitstreamsProcessor.java
new file mode 100644
index 0000000..8a6c74e
--- /dev/null
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/SupportedBitstreamsProcessor.java
@@ -0,0 +1,76 @@
+/*
+ * 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.media.cts.bitstreams;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Runs a test on target device to query support for bitstreams listed in device side
+ * dynamic configuration file.
+ */
+public class SupportedBitstreamsProcessor extends ReportProcessor {
+
+    private final String mPrefix;
+    private final boolean mDebugTargetDevice;
+    private final Set<String> mSupportedBitstreams = new LinkedHashSet<>();
+
+    public SupportedBitstreamsProcessor() {
+        this("",false);
+    }
+
+    /**
+     * @param prefix only bitstreams whose relative path starts with {@code prefix}
+     * would be processed
+     * @param debugTargetDevice whether to pause {@code device} for debugging
+     */
+    public SupportedBitstreamsProcessor(String prefix, boolean debugTargetDevice) {
+        mPrefix = prefix;
+        mDebugTargetDevice = debugTargetDevice;
+    }
+
+    /**
+     * @return paths of supported devices on device
+     */
+    public Set<String> getSupportedBitstreams() {
+        return mSupportedBitstreams;
+    }
+
+    @Override
+    Map<String, String> getArgs() {
+        Map<String, String> args = new HashMap<>();
+        args.put(MediaBitstreams.OPT_BITSTREAMS_PREFIX, mPrefix);
+        args.put(MediaBitstreams.OPT_DEBUG_TARGET_DEVICE, Boolean.toString(mDebugTargetDevice));
+        return args;
+    }
+
+    @Override
+    void process(ITestDevice device, String reportPath) throws DeviceNotAvailableException {
+        mSupportedBitstreams.clear();
+        for (String path: getReportLines(device, reportPath)) {
+            if (path.isEmpty()) {
+                continue;
+            }
+            mSupportedBitstreams.add(path);
+        }
+    }
+
+}
diff --git a/hostsidetests/security/AndroidTest.xml b/hostsidetests/security/AndroidTest.xml
index cd68c69..deb4f44 100644
--- a/hostsidetests/security/AndroidTest.xml
+++ b/hostsidetests/security/AndroidTest.xml
@@ -44,6 +44,7 @@
         <option name="push" value="CVE-2016-8434->/data/local/tmp/CVE-2016-8434" />
         <option name="push" value="CVE-2016-8435->/data/local/tmp/CVE-2016-8435" />
         <option name="push" value="CVE-2016-9120->/data/local/tmp/CVE-2016-9120" />
+	<option name="push" value="Bug-34328139->/data/local/tmp/Bug-34328139" />
         <option name="append-bitness" value="true" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/hostsidetests/security/securityPatch/Bug-34328139/Android.mk b/hostsidetests/security/securityPatch/Bug-34328139/Android.mk
new file mode 100644
index 0000000..cd8d541
--- /dev/null
+++ b/hostsidetests/security/securityPatch/Bug-34328139/Android.mk
@@ -0,0 +1,35 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := Bug-34328139
+LOCAL_SRC_FILES := poc.c
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+
+# Tag this module as a cts test artifact                                                                             
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_CTS_TEST_PACKAGE := android.security.cts
+
+LOCAL_ARM_MODE := arm
+CFLAGS += -Wall -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
+CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
+CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS += -Iinclude -fPIE
+LOCAL_LDFLAGS += -fPIE -pie
+LDFLAGS += -rdynamic
+include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/Bug-34328139/local_poc.h b/hostsidetests/security/securityPatch/Bug-34328139/local_poc.h
new file mode 100644
index 0000000..c14a36b
--- /dev/null
+++ b/hostsidetests/security/securityPatch/Bug-34328139/local_poc.h
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+#ifndef __CMD_H__
+#define __CMD_H__
+
+#define _IOC_NRBITS     8
+#define _IOC_TYPEBITS   8
+
+#ifndef _IOC_SIZEBITS
+# define _IOC_SIZEBITS  14
+#endif
+
+#ifndef _IOC_DIRBITS
+# define _IOC_DIRBITS   2
+#endif
+
+#define _IOC_NRMASK     ((1 << _IOC_NRBITS)-1)
+#define _IOC_TYPEMASK   ((1 << _IOC_TYPEBITS)-1)
+#define _IOC_SIZEMASK   ((1 << _IOC_SIZEBITS)-1)
+#define _IOC_DIRMASK    ((1 << _IOC_DIRBITS)-1)
+
+#define _IOC_NRSHIFT    0
+#define _IOC_TYPESHIFT  (_IOC_NRSHIFT+_IOC_NRBITS)
+#define _IOC_SIZESHIFT  (_IOC_TYPESHIFT+_IOC_TYPEBITS)
+#define _IOC_DIRSHIFT   (_IOC_SIZESHIFT+_IOC_SIZEBITS)
+
+#ifndef _IOC_NONE
+# define _IOC_NONE      0U
+#endif
+
+#ifndef _IOC_WRITE
+# define _IOC_WRITE     1U
+#endif
+
+#ifndef _IOC_READ
+# define _IOC_READ      2U
+#endif
+
+#define _IOC_TYPECHECK(t) (sizeof(t))
+#define _IOC(dir,type,nr,size) \
+        (((dir)  << _IOC_DIRSHIFT) | \
+         ((type) << _IOC_TYPESHIFT) | \
+         ((nr)   << _IOC_NRSHIFT) | \
+         ((size) << _IOC_SIZESHIFT))
+
+
+#define _IO(type,nr)            _IOC(_IOC_NONE,(type),(nr),0)
+#define _IOR(type,nr,size)      _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
+#define _IOW(type,nr,size)      _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
+#define _IOWR(type,nr,size)     _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
+
+
+
+struct mult_factor {
+	uint32_t numer;
+	uint32_t denom;
+};
+
+struct mdp_rotation_buf_info {
+	uint32_t width;
+	uint32_t height;
+	uint32_t format;
+	struct mult_factor comp_ratio;
+};
+
+struct mdp_rotation_config {
+	uint32_t	version;
+	uint32_t	session_id;
+	struct mdp_rotation_buf_info	input;
+	struct mdp_rotation_buf_info	output;
+	uint32_t	frame_rate;
+	uint32_t	flags;
+	uint32_t	reserved[6];
+};
+
+#define MDSS_ROTATOR_IOCTL_MAGIC 'w'
+
+#define MDSS_ROTATION_OPEN \
+	_IOWR(MDSS_ROTATOR_IOCTL_MAGIC, 1, struct mdp_rotation_config *)
+
+#define MDSS_ROTATION_CONFIG \
+	_IOWR(MDSS_ROTATOR_IOCTL_MAGIC, 2, struct mdp_rotation_config *)
+
+#define MDSS_ROTATION_CLOSE	_IOW(MDSS_ROTATOR_IOCTL_MAGIC, 4, unsigned int)
+#endif
diff --git a/hostsidetests/security/securityPatch/Bug-34328139/poc.c b/hostsidetests/security/securityPatch/Bug-34328139/poc.c
new file mode 100644
index 0000000..64337fd
--- /dev/null
+++ b/hostsidetests/security/securityPatch/Bug-34328139/poc.c
@@ -0,0 +1,141 @@
+/*
+ * 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 _GNU_SOURCE
+
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include "local_poc.h"
+
+
+int fd;
+struct mdp_rotation_config config;
+int id;
+int status[10];
+int cmd = 0;
+
+void *threadForConfig(void *arg)
+{
+    int index = (int) (unsigned long)arg;
+
+    status[index] = 1;
+
+    while (cmd != 1) {
+        usleep(10);
+    }
+    
+    if (cmd == -1)
+        goto failed;
+
+    usleep(5 * index);
+    ioctl(fd, MDSS_ROTATION_CONFIG, &config);
+failed:
+    status[index] = 2;
+    return NULL;
+
+}
+
+void *threadForClose()
+{
+    status[0] = 1;
+    
+    while (cmd != 1) {
+        usleep(10);
+    }
+
+     if (cmd == -1)
+        goto failed;
+
+    usleep(50);
+    ioctl(fd, MDSS_ROTATION_CLOSE, id);
+failed:
+    status[0] = 2;   
+    return NULL;
+}
+
+int main()
+{
+    int ret, i, count;
+    pthread_t tid[5];
+    int p = 5;    
+ 
+    count = 0;
+retry:
+    if (p-- > 0){
+        fork();
+    }
+    
+    cmd = 0;
+    for (i = 0; i < 10; i++)
+        status[i] = 0;
+    
+    fd = open("/dev/mdss_rotator", O_RDONLY, 0);
+    if (fd < 0) {
+	return -1;
+    }
+
+    ret = ioctl(fd, MDSS_ROTATION_OPEN, &config);
+    if (ret < 0) {
+	goto failed;
+    } else {
+        id = config.session_id;
+    }
+    
+    ret = pthread_create(&tid[0], NULL, threadForClose, NULL);
+    if (ret != 0) {
+        printf("thread failed! errno:%d err:%s\n",errno,strerror(errno));
+        goto failed;
+    }
+    
+    for (i = 1; i < 10; i++) {
+        ret = pthread_create(&tid[1], NULL, threadForConfig, (void *)(unsigned long)i);
+        if (ret != 0) {
+            cmd = -1;
+            goto failed;
+        }
+    }
+
+    while (status[0] != 1 || status[1] != 1 || status[2] != 1
+        || status[3] != 1 || status[4] != 1 || status[5] != 1
+        || status[6] != 1 || status[7] != 1 || status[8] != 1
+        || status[9] != 1) {
+        usleep(50);
+    }
+
+    cmd = 1;
+    usleep(10);
+    ioctl(fd, MDSS_ROTATION_CONFIG, &config);
+
+    while (status[0] != 2 || status[1] != 2 || status[2] != 2
+        || status[3] != 2 || status[4] != 2 || status[5] != 2
+        || status[6] != 2 || status[7] != 2 || status[8] != 2
+        || status[9] != 2) {
+        usleep(50);
+    }
+    
+    	
+failed:
+    close(fd);
+    printf("[pid:%d] try %d again!\n", getpid(), ++count);
+    goto retry;
+
+    return 0;	
+}
diff --git a/hostsidetests/security/src/android/security/cts/Poc17_06.java b/hostsidetests/security/src/android/security/cts/Poc17_06.java
new file mode 100644
index 0000000..64bc70e
--- /dev/null
+++ b/hostsidetests/security/src/android/security/cts/Poc17_06.java
@@ -0,0 +1,34 @@
+/**
+ * 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.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+
+@SecurityTest
+public class Poc17_06 extends SecurityTestCase {
+
+    /**
+     *  b/34328139
+     */
+    @SecurityTest
+    public void testPocBug_34328139() throws Exception {
+	enableAdbRoot(getDevice());
+        if(containsDriver(getDevice(), "/dev/mdss_rotator")) {
+            AdbUtils.runPoc("Bug-34328139", getDevice(), 60);
+        }
+    }
+}
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 f3ce776..d4a546b 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
@@ -40,8 +40,8 @@
     private static final String NIGHT_MODE_ACTIVITY = "NightModeActivity";
     private static final String DIALOG_WHEN_LARGE_ACTIVITY = "DialogWhenLargeActivity";
 
-    private static final String TRANSLUCENT_ACTIVITY =
-            "android.server.translucentapp.TranslucentLandscapeActivity";
+    private static final String TRANSLUCENT_ACTIVITY = "TranslucentLandscapeActivity";
+
     private static final String TRANSLUCENT_CURRENT_PACKAGE = "android.server.translucentapp";
 
     private static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
@@ -332,15 +332,17 @@
 //                TRANSLUCENT_ACTIVITY);
 //    }
 
-    public void testLegacyNonFullscreenActivityPermitted() throws Exception {
+    // TODO(b/38225467): rename to testLegacyNonFullscreenActivityPermitted
+    public void testNonFullscreenActivityPermitted() throws Exception {
         // TODO(b/38225467): Target SDK 26 specific package when SDK 27 released.
         setComponentName(TRANSLUCENT_CURRENT_PACKAGE);
         setDeviceRotation(0);
+
         launchActivity(TRANSLUCENT_ACTIVITY);
         mAmWmState.assertResumedActivity(
-                "target SDK <= 26 non-fullscreen activitiy should be allowed to launch",
+                "target SDK <= 26 non-fullscreen activity should be allowed to launch",
                 TRANSLUCENT_ACTIVITY);
-        assertEquals("non-fullscreen activitiy requested landscape orientation",
+        assertEquals("non-fullscreen activity requested landscape orientation",
                 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
     }
 
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 f54e83b..b1cf2a2 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
@@ -185,8 +185,13 @@
         return getActivityComponentName(componentName, activityName);
     }
 
+    private static boolean isFullyQualifiedActivityName(String name) {
+        return name != null && name.contains(".");
+    }
+
     static String getActivityComponentName(final String packageName, final String activityName) {
-        return packageName + "/" + (activityName.contains(".") ? "" : ".") + activityName;
+        return packageName + "/" + (isFullyQualifiedActivityName(activityName) ? "" : ".") +
+                activityName;
     }
 
     // A little ugly, but lets avoid having to strip static everywhere for
@@ -212,7 +217,8 @@
     }
 
     static String getWindowName(final String packageName, final String activityName) {
-        return getBaseWindowName(packageName, !activityName.contains(".")) + activityName;
+        return getBaseWindowName(packageName, !isFullyQualifiedActivityName(activityName))
+                + activityName;
     }
 
     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
diff --git a/hostsidetests/theme/assets/O/360dpi.zip b/hostsidetests/theme/assets/26/360dpi.zip
similarity index 100%
rename from hostsidetests/theme/assets/O/360dpi.zip
rename to hostsidetests/theme/assets/26/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/26/420dpi.zip b/hostsidetests/theme/assets/26/420dpi.zip
new file mode 100644
index 0000000..8f9f743
--- /dev/null
+++ b/hostsidetests/theme/assets/26/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/26/560dpi.zip b/hostsidetests/theme/assets/26/560dpi.zip
new file mode 100644
index 0000000..676fe4c
--- /dev/null
+++ b/hostsidetests/theme/assets/26/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/26/hdpi.zip b/hostsidetests/theme/assets/26/hdpi.zip
new file mode 100644
index 0000000..0d0d4ad
--- /dev/null
+++ b/hostsidetests/theme/assets/26/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/26/mdpi.zip b/hostsidetests/theme/assets/26/mdpi.zip
new file mode 100644
index 0000000..98938fe
--- /dev/null
+++ b/hostsidetests/theme/assets/26/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/26/xhdpi.zip b/hostsidetests/theme/assets/26/xhdpi.zip
new file mode 100644
index 0000000..6b949d2
--- /dev/null
+++ b/hostsidetests/theme/assets/26/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/26/xxhdpi.zip b/hostsidetests/theme/assets/26/xxhdpi.zip
new file mode 100644
index 0000000..03817b9
--- /dev/null
+++ b/hostsidetests/theme/assets/26/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/26/xxxhdpi.zip b/hostsidetests/theme/assets/26/xxxhdpi.zip
new file mode 100644
index 0000000..a5c808f
--- /dev/null
+++ b/hostsidetests/theme/assets/26/xxxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/O/420dpi.zip b/hostsidetests/theme/assets/O/420dpi.zip
deleted file mode 100644
index bc887a0..0000000
--- a/hostsidetests/theme/assets/O/420dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/O/560dpi.zip b/hostsidetests/theme/assets/O/560dpi.zip
deleted file mode 100644
index cc33325..0000000
--- a/hostsidetests/theme/assets/O/560dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/O/hdpi.zip b/hostsidetests/theme/assets/O/hdpi.zip
deleted file mode 100644
index 094f1e7..0000000
--- a/hostsidetests/theme/assets/O/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/O/mdpi.zip b/hostsidetests/theme/assets/O/mdpi.zip
deleted file mode 100644
index e1cfa65..0000000
--- a/hostsidetests/theme/assets/O/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/O/xhdpi.zip b/hostsidetests/theme/assets/O/xhdpi.zip
deleted file mode 100644
index 5f39517..0000000
--- a/hostsidetests/theme/assets/O/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/O/xxhdpi.zip b/hostsidetests/theme/assets/O/xxhdpi.zip
deleted file mode 100644
index 66b4a83..0000000
--- a/hostsidetests/theme/assets/O/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/O/xxxhdpi.zip b/hostsidetests/theme/assets/O/xxxhdpi.zip
deleted file mode 100644
index 979fb28..0000000
--- a/hostsidetests/theme/assets/O/xxxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 4ade3b5..0ca3ecc 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -88,7 +88,7 @@
         TestWorkItem[] expectedWork = TestEnvironment.getTestEnvironment().getExpectedWork();
         if (expectedWork != null) {
             try {
-                if (TestEnvironment.getTestEnvironment().awaitDoWork()) {
+                if (!TestEnvironment.getTestEnvironment().awaitDoWork()) {
                     TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
                             permCheckWrite, null, "Spent too long waiting to start executing work");
                     return false;
@@ -200,15 +200,29 @@
             // stops itself.
             return true;
         } else {
+            boolean continueAfterStart
+                    = TestEnvironment.getTestEnvironment().handleContinueAfterStart();
+            try {
+                if (!TestEnvironment.getTestEnvironment().awaitDoJob()) {
+                    TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
+                            permCheckWrite, null, "Spent too long waiting to start job");
+                    return false;
+                }
+            } catch (InterruptedException e) {
+                TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
+                        permCheckWrite, null, "Failed waiting to start job: " + e);
+                return false;
+            }
             TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
                     permCheckWrite, null, null);
-            return false;  // No work to do.
+            return continueAfterStart;
         }
     }
 
     @Override
     public boolean onStopJob(JobParameters params) {
         Log.i(TAG, "Received stop callback");
+        TestEnvironment.getTestEnvironment().notifyStopped();
         return mWaitingForStop;
     }
 
@@ -282,8 +296,11 @@
 
         private CountDownLatch mLatch;
         private CountDownLatch mWaitingForStopLatch;
+        private CountDownLatch mDoJobLatch;
+        private CountDownLatch mStoppedLatch;
         private CountDownLatch mDoWorkLatch;
         private TestWorkItem[] mExpectedWork;
+        private boolean mContinueAfterStart;
         private JobParameters mExecutedJobParameters;
         private int mExecutedPermCheckRead;
         private int mExecutedPermCheckWrite;
@@ -326,7 +343,11 @@
          * job on this service.
          */
         public boolean awaitExecution() throws InterruptedException {
-            final boolean executed = mLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            return awaitExecution(DEFAULT_TIMEOUT_MILLIS);
+        }
+
+        public boolean awaitExecution(long timeoutMillis) throws InterruptedException {
+            final boolean executed = mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
             if (getLastErrorMessage() != null) {
                 Assert.fail(getLastErrorMessage());
             }
@@ -343,11 +364,22 @@
         }
 
         public boolean awaitWaitingForStop() throws InterruptedException {
-            return !mWaitingForStopLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            return mWaitingForStopLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }
 
         public boolean awaitDoWork() throws InterruptedException {
-            return !mDoWorkLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            return mDoWorkLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        }
+
+        public boolean awaitDoJob() throws InterruptedException {
+            if (mDoJobLatch == null) {
+                return true;
+            }
+            return mDoJobLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        }
+
+        public boolean awaitStopped() throws InterruptedException {
+            return mStoppedLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }
 
         private void notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite,
@@ -365,6 +397,12 @@
             mWaitingForStopLatch.countDown();
         }
 
+        private void notifyStopped() {
+            if (mStoppedLatch != null) {
+                mStoppedLatch.countDown();
+            }
+        }
+
         public void setExpectedExecutions(int numExecutions) {
             // For no executions expected, set count to 1 so we can still block for the timeout.
             if (numExecutions == 0) {
@@ -372,6 +410,12 @@
             } else {
                 mLatch = new CountDownLatch(numExecutions);
             }
+            mWaitingForStopLatch = null;
+            mDoJobLatch = null;
+            mStoppedLatch = null;
+            mDoWorkLatch = null;
+            mExpectedWork = null;
+            mContinueAfterStart = false;
         }
 
         public void setExpectedWaitForStop() {
@@ -383,10 +427,32 @@
             mDoWorkLatch = new CountDownLatch(1);
         }
 
+        public void setExpectedStopped() {
+            mStoppedLatch = new CountDownLatch(1);
+        }
+
         public void readyToWork() {
             mDoWorkLatch.countDown();
         }
 
+        public void setExpectedWaitForRun() {
+            mDoJobLatch = new CountDownLatch(1);
+        }
+
+        public void readyToRun() {
+            mDoJobLatch.countDown();
+        }
+
+        public void setContinueAfterStart() {
+            mContinueAfterStart = true;
+        }
+
+        public boolean handleContinueAfterStart() {
+            boolean res = mContinueAfterStart;
+            mContinueAfterStart = false;
+            return res;
+        }
+
         /** Called in each testCase#setup */
         public void setUp() {
             mLatch = null;
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
index 5ffa347..65cd02b 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
@@ -76,15 +76,52 @@
 
         // Wait for the battery update to be processed by job scheduler before proceeding.
         int curSeq;
+        boolean curCharging;
         do {
+            Thread.sleep(50);
             curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
                     "cmd jobscheduler get-battery-seq").trim());
-            if (curSeq == seq) {
+            // The job scheduler actually looks at the charging/discharging state,
+            // which is currently determined by battery stats in response to the low-level
+            // plugged/unplugged events.  So we can get this updated after the last seq
+            // is received, so we need to make sure that has correctly changed.
+            curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
+                    "cmd jobscheduler get-battery-charging").trim());
+            if (curSeq == seq && curCharging == plugged) {
                 return;
             }
-        } while ((SystemClock.elapsedRealtime()-startTime) < 1000);
+        } while ((SystemClock.elapsedRealtime()-startTime) < 5000);
 
-        fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq);
+        fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq
+                + ", plugged=" + plugged + " curCharging=" + curCharging);
+    }
+
+    void verifyChargingState(boolean charging) throws Exception {
+        boolean curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
+                "cmd jobscheduler get-battery-charging").trim());
+        assertEquals(charging, curCharging);
+    }
+
+    void verifyBatteryNotLowState(boolean notLow) throws Exception {
+        boolean curNotLow = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
+                "cmd jobscheduler get-battery-not-low").trim());
+        assertEquals(notLow, curNotLow);
+    }
+
+    String getJobState() throws Exception {
+        return getJobState(BATTERY_JOB_ID);
+    }
+
+    void assertJobReady() throws Exception {
+        assertJobReady(BATTERY_JOB_ID);
+    }
+
+    void assertJobWaiting() throws Exception {
+        assertJobWaiting(BATTERY_JOB_ID);
+    }
+
+    void assertJobNotReady() throws Exception {
+        assertJobNotReady(BATTERY_JOB_ID);
     }
 
     // --------------------------------------------------------------------------------------------
@@ -97,9 +134,13 @@
      */
     public void testChargingConstraintExecutes() throws Exception {
         setBatteryState(true, 100);
+        verifyChargingState(true);
 
         kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForRun();
         mJobScheduler.schedule(mBuilder.setRequiresCharging(true).build());
+        assertJobReady();
+        kTestEnvironment.readyToRun();
 
         assertTrue("Job with charging constraint did not fire on power.",
                 kTestEnvironment.awaitExecution());
@@ -111,9 +152,14 @@
      */
     public void testBatteryNotLowConstraintExecutes_withPower() throws Exception {
         setBatteryState(true, 100);
+        verifyChargingState(true);
+        verifyBatteryNotLowState(true);
 
         kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForRun();
         mJobScheduler.schedule(mBuilder.setRequiresBatteryNotLow(true).build());
+        assertJobReady();
+        kTestEnvironment.readyToRun();
 
         assertTrue("Job with battery not low constraint did not fire on power.",
                 kTestEnvironment.awaitExecution());
@@ -125,9 +171,14 @@
      */
     public void testBatteryNotLowConstraintExecutes_withoutPower() throws Exception {
         setBatteryState(false, 100);
+        verifyChargingState(false);
+        verifyBatteryNotLowState(true);
 
         kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForRun();
         mJobScheduler.schedule(mBuilder.setRequiresBatteryNotLow(true).build());
+        assertJobReady();
+        kTestEnvironment.readyToRun();
 
         assertTrue("Job with battery not low constraint did not fire on power.",
                 kTestEnvironment.awaitExecution());
@@ -140,22 +191,40 @@
     /**
      * Schedule a job that requires the device is charging, and assert if failed when
      * the device is not on power.
-     * TODO: turned off for now due to flakiness.
      */
-    public void xxxtestChargingConstraintFails() throws Exception {
+    public void testChargingConstraintFails() throws Exception {
         setBatteryState(false, 100);
+        verifyChargingState(false);
 
         kTestEnvironment.setExpectedExecutions(0);
+        kTestEnvironment.setExpectedWaitForRun();
         mJobScheduler.schedule(mBuilder.setRequiresCharging(true).build());
+        assertJobWaiting();
+        assertJobNotReady();
+        kTestEnvironment.readyToRun();
 
         assertFalse("Job with charging constraint fired while not on power.",
-                kTestEnvironment.awaitExecution());
+                kTestEnvironment.awaitExecution(250));
+        assertJobWaiting();
+        assertJobNotReady();
 
-        // And for good measure, ensure the job runs once the device is plugged in.
+        // Ensure the job runs once the device is plugged in.
         kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForRun();
+        kTestEnvironment.setContinueAfterStart();
         setBatteryState(true, 100);
+        verifyChargingState(true);
+        kTestEnvironment.setExpectedStopped();
+        assertJobReady();
+        kTestEnvironment.readyToRun();
         assertTrue("Job with charging constraint did not fire on power.",
                 kTestEnvironment.awaitExecution());
+
+        // And check that the job is stopped if the device is unplugged while it is running.
+        setBatteryState(false, 100);
+        verifyChargingState(false);
+        assertTrue("Job with charging constraint did not stop when power removed.",
+                kTestEnvironment.awaitStopped());
     }
 
     /**
@@ -164,17 +233,40 @@
      */
     public void testBatteryNotLowConstraintFails_withoutPower() throws Exception {
         setBatteryState(false, 15);
+        verifyChargingState(false);
+        verifyBatteryNotLowState(false);
 
         kTestEnvironment.setExpectedExecutions(0);
+        kTestEnvironment.setExpectedWaitForRun();
         mJobScheduler.schedule(mBuilder.setRequiresBatteryNotLow(true).build());
+        assertJobWaiting();
+        assertJobNotReady();
+        kTestEnvironment.readyToRun();
 
         assertFalse("Job with battery not low constraint fired while level critical.",
-                kTestEnvironment.awaitExecution());
+                kTestEnvironment.awaitExecution(250));
+        assertJobWaiting();
+        assertJobNotReady();
 
-        // And for good measure, ensure the job runs once the device's battery level is not low.
+        // Ensure the job runs once the device's battery level is not low.
         kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForRun();
+        kTestEnvironment.setContinueAfterStart();
         setBatteryState(false, 50);
+        verifyChargingState(false);
+        verifyBatteryNotLowState(true);
+        kTestEnvironment.setExpectedStopped();
+        assertJobReady();
+        kTestEnvironment.readyToRun();
         assertTrue("Job with not low constraint did not fire when charge increased.",
                 kTestEnvironment.awaitExecution());
+
+        // And check that the job is stopped if battery goes low again.
+        setBatteryState(false, 15);
+        setBatteryState(false, 14);
+        verifyChargingState(false);
+        verifyBatteryNotLowState(false);
+        assertTrue("Job with not low constraint did not stop when battery went low.",
+                kTestEnvironment.awaitStopped());
     }
 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConstraintTest.java
index bcc1e08..f3a895e 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConstraintTest.java
@@ -181,4 +181,25 @@
 
         fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq);
     }
+
+    String getJobState(int jobId) throws Exception {
+        return SystemUtil.runShellCommand(getInstrumentation(),
+                "cmd jobscheduler get-job-state " + kJobServiceComponent.getPackageName()
+                        + " " + jobId).trim();
+    }
+
+    void assertJobReady(int jobId) throws Exception {
+        String state = getJobState(jobId);
+        assertTrue("Job unexpectedly not ready, in state: " + state, state.contains("ready"));
+    }
+
+    void assertJobWaiting(int jobId) throws Exception {
+        String state = getJobState(jobId);
+        assertTrue("Job unexpectedly not waiting, in state: " + state, state.contains("waiting"));
+    }
+
+    void assertJobNotReady(int jobId) throws Exception {
+        String state = getJobState(jobId);
+        assertTrue("Job unexpectedly ready, in state: " + state, !state.contains("ready"));
+    }
 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
index ff9a57b..6d2599f 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
@@ -210,7 +210,7 @@
 
         // Now wait for the job to get to the point where it is processing the last
         // work and waiting for it to be stopped.
-        assertFalse("Job with work enqueued did not wait to stop.",
+        assertTrue("Job with work enqueued did not wait to stop.",
                 kTestEnvironment.awaitWaitingForStop());
 
         // Cause the job to timeout (stop) immediately, and wait for its execution to finish.
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java
index aea4d84..d2d9cae 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java
@@ -48,6 +48,22 @@
         mJobScheduler.cancel(STORAGE_JOB_ID);
     }
 
+    String getJobState() throws Exception {
+        return getJobState(STORAGE_JOB_ID);
+    }
+
+    void assertJobReady() throws Exception {
+        assertJobReady(STORAGE_JOB_ID);
+    }
+
+    void assertJobWaiting() throws Exception {
+        assertJobWaiting(STORAGE_JOB_ID);
+    }
+
+    void assertJobNotReady() throws Exception {
+        assertJobNotReady(STORAGE_JOB_ID);
+    }
+
     // --------------------------------------------------------------------------------------------
     // Positives - schedule jobs under conditions that require them to pass.
     // --------------------------------------------------------------------------------------------
@@ -59,7 +75,10 @@
         setStorageState(false);
 
         kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForRun();
         mJobScheduler.schedule(mBuilder.setRequiresStorageNotLow(true).build());
+        assertJobReady();
+        kTestEnvironment.readyToRun();
 
         assertTrue("Job with storage not low constraint did not fire when storage not low.",
                 kTestEnvironment.awaitExecution());
@@ -76,14 +95,21 @@
         setStorageState(true);
 
         kTestEnvironment.setExpectedExecutions(0);
+        kTestEnvironment.setExpectedWaitForRun();
         mJobScheduler.schedule(mBuilder.setRequiresStorageNotLow(true).build());
+        assertJobWaiting();
+        assertJobNotReady();
+        kTestEnvironment.readyToRun();
 
         assertFalse("Job with storage now low constraint fired while low.",
-                kTestEnvironment.awaitExecution());
+                kTestEnvironment.awaitExecution(250));
 
         // And for good measure, ensure the job runs once storage is okay.
         kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForRun();
         setStorageState(false);
+        assertJobReady();
+        kTestEnvironment.readyToRun();
         assertTrue("Job with storage not low constraint did not fire when storage not low.",
                 kTestEnvironment.awaitExecution());
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
index 82607b3..b21f9c4 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
@@ -62,6 +62,7 @@
         assertThat(mFragment).isNotNull();
     }
 
+    // firstRemove and secondRemove run in the UI Thread; firstCheck doesn't
     private void removeViewsBaseTest(@NonNull Runnable firstRemove, @Nullable Runnable firstCheck,
             @Nullable Runnable secondRemove, String... viewsToSave)
             throws Exception {
@@ -74,8 +75,10 @@
 
             // Trigger autofill
             eventually(() -> {
-                mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
-                mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
+                mActivity.syncRunOnUiThread(() -> {
+                    mEditText2.requestFocus();
+                    mEditText1.requestFocus();
+                });
 
                 try {
                     sReplier.getNextFillRequest();
@@ -90,8 +93,8 @@
             mActivity.syncRunOnUiThread(() -> {
                 mEditText1.setText("editText1-filled");
                 mEditText2.setText("editText2-filled");
+                firstRemove.run();
             });
-            firstRemove.run();
 
             // Check state between remove operations
             if (firstCheck != null) {
@@ -100,7 +103,7 @@
 
             // remove second set of views
             if (secondRemove != null) {
-                secondRemove.run();
+                mActivity.syncRunOnUiThread(secondRemove);
             }
 
             // Save should be shows after all remove operations were executed
@@ -119,23 +122,21 @@
     @Test
     public void removeBothViewsToFinishSession() throws Exception {
         removeViewsBaseTest(
-                () -> mActivity.syncRunOnUiThread(
-                        () -> ((ViewGroup) mEditText1.getParent()).removeView(mEditText1)),
+                () -> ((ViewGroup) mEditText1.getParent()).removeView(mEditText1),
                 () -> sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC),
-                () -> mActivity.syncRunOnUiThread(
-                        () -> ((ViewGroup) mEditText2.getParent()).removeView(mEditText2)),
+                () -> ((ViewGroup) mEditText2.getParent()).removeView(mEditText2),
                 "editText1", "editText2");
     }
 
     @Test
     public void removeOneViewToFinishSession() throws Exception {
         removeViewsBaseTest(
-                () -> mActivity.syncRunOnUiThread(() -> {
+                () -> {
                     // Do not trigger new partition when switching to editText2
                     mEditText2.setFocusable(false);
 
                     mParent.removeView(mEditText1);
-                }),
+                },
                 null,
                 null,
                 "editText1");
@@ -144,12 +145,12 @@
     @Test
     public void hideOneViewToFinishSession() throws Exception {
         removeViewsBaseTest(
-                () -> mActivity.syncRunOnUiThread(() -> {
+                () -> {
                     // Do not trigger new partition when switching to editText2
                     mEditText2.setFocusable(false);
 
                     mEditText1.setVisibility(ViewGroup.INVISIBLE);
-                }),
+                },
                 null,
                 null,
                 "editText1");
@@ -158,9 +159,8 @@
     @Test
     public void removeFragmentToFinishSession() throws Exception {
         removeViewsBaseTest(
-                () -> mActivity.syncRunOnUiThread(
-                        () -> mActivity.getFragmentManager().beginTransaction().remove(
-                                mFragment).commitNow()),
+                () -> mActivity.getFragmentManager().beginTransaction().remove(
+                        mFragment).commitNow(),
                 null,
                 null,
                 "editText1", "editText2");
@@ -169,8 +169,7 @@
     @Test
     public void removeParentToFinishSession() throws Exception {
         removeViewsBaseTest(
-                () -> mActivity.syncRunOnUiThread(
-                        () -> ((ViewGroup) mParent.getParent()).removeView(mParent)),
+                () -> ((ViewGroup) mParent.getParent()).removeView(mParent),
                 null,
                 null,
                 "editText1", "editText2");
@@ -179,7 +178,7 @@
     @Test
     public void hideParentToFinishSession() throws Exception {
         removeViewsBaseTest(
-                () -> mActivity.syncRunOnUiThread(() -> mParent.setVisibility(ViewGroup.INVISIBLE)),
+                () -> mParent.setVisibility(ViewGroup.INVISIBLE),
                 null,
                 null,
                 "editText1", "editText2");
@@ -188,6 +187,8 @@
     /**
      * An activity that is currently getting autofilled might go into the background. While the
      * tracked views are not visible on the screen anymore, this should not trigger a save.
+     *
+     * <p>The {@link Runnable}s are synchronously run in the UI thread.
      */
     public void activityToBackgroundShouldNotTriggerSave(@Nullable Runnable removeInBackGround,
             @Nullable Runnable removeInForeGroup) throws Exception {
@@ -200,8 +201,10 @@
 
             // Trigger autofill
             eventually(() -> {
-                mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
-                mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
+                mActivity.syncRunOnUiThread(() -> {
+                    mEditText2.requestFocus();
+                    mEditText1.requestFocus();
+                });
 
                 try {
                     sReplier.getNextFillRequest();
@@ -223,7 +226,7 @@
             mActivity.waitUntilStopped();
 
             if (removeInBackGround != null) {
-                removeInBackGround.run();
+                mActivity.syncRunOnUiThread(removeInBackGround);
             }
 
             sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
@@ -235,7 +238,7 @@
             if (removeInForeGroup != null) {
                 sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
 
-                removeInForeGroup.run();
+                mActivity.syncRunOnUiThread(removeInForeGroup);
             }
 
             // Save should be shows after all remove operations were executed
@@ -263,41 +266,35 @@
 
     @Test
     public void hideViewInBackground() throws Exception {
-        activityToBackgroundShouldNotTriggerSave(
-                () -> mActivity.syncRunOnUiThread(() -> {
+        activityToBackgroundShouldNotTriggerSave(() -> {
                     // Do not trigger new partition when switching to editText2
                     mEditText2.setFocusable(false);
 
                     mEditText1.setVisibility(ViewGroup.INVISIBLE);
-                }),
+                },
                 null);
     }
 
     @Test
     public void hideParentInBackground() throws Exception {
-        activityToBackgroundShouldNotTriggerSave(
-                () -> mActivity.syncRunOnUiThread(() -> mParent.setVisibility(ViewGroup.INVISIBLE)),
+        activityToBackgroundShouldNotTriggerSave(() -> mParent.setVisibility(ViewGroup.INVISIBLE),
                 null);
     }
 
     @Test
     public void removeParentInBackground() throws Exception {
         activityToBackgroundShouldNotTriggerSave(
-                () -> mActivity.syncRunOnUiThread(
-                        () -> ((ViewGroup) mParent.getParent()).removeView(mParent)),
+                () -> ((ViewGroup) mParent.getParent()).removeView(mParent),
                 null);
     }
 
     @Test
     public void removeViewAfterBackground() throws Exception {
-        activityToBackgroundShouldNotTriggerSave(
-                () -> mActivity.syncRunOnUiThread(() -> {
+        activityToBackgroundShouldNotTriggerSave(() -> {
                     // Do not trigger new fill request when closing activity
                     mEditText1.setFocusable(false);
                     mEditText2.setFocusable(false);
-                }),
-                () -> mActivity.syncRunOnUiThread(() -> {
-                    mParent.removeView(mEditText1);
-                }));
+                },
+                () -> mParent.removeView(mEditText1));
     }
 }
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverSyncTestCase.java b/tests/tests/content/src/android/content/cts/ContentResolverSyncTestCase.java
index 2da0bfb..86e66cc 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverSyncTestCase.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverSyncTestCase.java
@@ -39,6 +39,7 @@
     private static final Account ACCOUNT = new Account(MockAccountAuthenticator.ACCOUNT_NAME,
             MockAccountAuthenticator.ACCOUNT_TYPE);
 
+    private static final int INITIAL_SYNC_TIMEOUT_MS = 60 * 1000;
     private static final int LATCH_TIMEOUT_MS = 5000;
 
     private static AccountManager sAccountManager;
@@ -96,7 +97,7 @@
     }
 
     private void addAccountAndVerifyInitSync(Account account, String password,
-            String authority, int latchTimeoutMs, int accountIndex) {
+            String authority, int accountIndex) {
 
         CountDownLatch latch = setNewLatch(new CountDownLatch(1));
 
@@ -104,7 +105,7 @@
 
         // Wait with timeout for the callback to do its work
         try {
-            if (!latch.await(latchTimeoutMs, TimeUnit.MILLISECONDS)) {
+            if (!latch.await(INITIAL_SYNC_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                 fail("should not time out waiting on latch");
             }
         } catch (InterruptedException e) {
@@ -167,7 +168,6 @@
         addAccountAndVerifyInitSync(ACCOUNT,
                 MockAccountAuthenticator.ACCOUNT_PASSWORD,
                 AUTHORITY,
-                LATCH_TIMEOUT_MS,
                 0);
 
         getMockSyncAdapter().clearData();
@@ -199,7 +199,6 @@
         addAccountAndVerifyInitSync(ACCOUNT,
                 MockAccountAuthenticator.ACCOUNT_PASSWORD,
                 AUTHORITY,
-                LATCH_TIMEOUT_MS,
                 0);
 
         getMockSyncAdapter().clearData();
@@ -343,7 +342,6 @@
         addAccountAndVerifyInitSync(ACCOUNT,
                 MockAccountAuthenticator.ACCOUNT_PASSWORD,
                 AUTHORITY,
-                LATCH_TIMEOUT_MS,
                 0);
 
         getMockSyncAdapter().clearData();
diff --git a/tests/tests/content/src/android/content/cts/MockSyncAdapter.java b/tests/tests/content/src/android/content/cts/MockSyncAdapter.java
index df24749..957859e 100644
--- a/tests/tests/content/src/android/content/cts/MockSyncAdapter.java
+++ b/tests/tests/content/src/android/content/cts/MockSyncAdapter.java
@@ -30,13 +30,13 @@
 
     private static MockSyncAdapter sSyncAdapter = null;
 
-    private ArrayList<Account> mAccounts = new ArrayList<Account>();
-    private String mAuthority;
-    private Bundle mExtras;
-    private boolean mInitialized;
-    private boolean mStartSync;
-    private boolean mCancelSync;
-    private CountDownLatch mLatch;
+    private volatile ArrayList<Account> mAccounts = new ArrayList<Account>();
+    private volatile String mAuthority;
+    private volatile Bundle mExtras;
+    private volatile boolean mInitialized;
+    private volatile boolean mStartSync;
+    private volatile boolean mCancelSync;
+    private volatile CountDownLatch mLatch;
 
     public ArrayList<Account> getAccounts() {
         return mAccounts;
@@ -93,9 +93,7 @@
             mCancelSync = false;
         }
 
-        if (null != mLatch) {
-            mLatch.countDown();
-        }
+        countDownLatch();
     }
 
     public void cancelSync(ISyncContext syncContext) throws RemoteException {
@@ -107,9 +105,7 @@
         mStartSync = false;
         mCancelSync = true;
 
-        if (null != mLatch) {
-            mLatch.countDown();
-        }
+        countDownLatch();
     }
 
     public void initialize(android.accounts.Account account, java.lang.String authority)
@@ -122,8 +118,13 @@
         mStartSync = false;
         mCancelSync = false;
 
-        if (null != mLatch) {
-            mLatch.countDown();
+        countDownLatch();
+    }
+
+    private void countDownLatch() {
+        final CountDownLatch latch = mLatch;
+        if (latch != null) {
+            latch.countDown();
         }
     }
 
diff --git a/tests/tests/graphics/res/drawable/inset_color_fraction.xml b/tests/tests/graphics/res/drawable/inset_color_fraction.xml
new file mode 100644
index 0000000..6723b2e
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/inset_color_fraction.xml
@@ -0,0 +1,20 @@
+<?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.
+ -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:drawable="@android:color/white"
+       android:inset="10%" />
\ No newline at end of file
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
index 2d42f3c..fd1ed3e 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
@@ -75,6 +75,9 @@
 
         new InsetDrawable(null, -1);
         new InsetDrawable(null, -1, -1, -1, -1);
+
+        new InsetDrawable(mPassDrawable, .1f);
+        new InsetDrawable(mPassDrawable, .1f, .1f, .1f, .1f);
     }
 
     @Test
@@ -93,6 +96,7 @@
         }
     }
 
+
     @Test(expected=NullPointerException.class)
     public void testInflateNull() throws Throwable {
         InsetDrawable insetDrawable = new InsetDrawable(null, 0);
@@ -374,9 +378,9 @@
         expected = -1;
         assertEquals(expected, mPassDrawable.getIntrinsicWidth());
 
-        mInsetDrawable = new InsetDrawable(mPassDrawable, .2f);
+        mPassDrawable = mContext.getDrawable(R.drawable.inset_color_fraction);
         expected = (int)(mPassDrawable.getIntrinsicWidth() * (1.4f));
-        assertEquals(expected, mInsetDrawable.getIntrinsicWidth());
+        assertEquals(expected, mPassDrawable.getIntrinsicWidth());
     }
 
     @Test
@@ -400,9 +404,9 @@
         expected = -1;
         assertEquals(expected, mPassDrawable.getIntrinsicHeight());
 
-        mInsetDrawable = new InsetDrawable(mPassDrawable, .2f);
+        mPassDrawable = mContext.getDrawable(R.drawable.inset_color_fraction);
         expected = (int)(mPassDrawable.getIntrinsicHeight() * (1.4f));
-        assertEquals(expected, mInsetDrawable.getIntrinsicHeight());
+        assertEquals(expected, mPassDrawable.getIntrinsicHeight());
     }
 
     @Test
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index b7f7c8e..7457b63 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -493,10 +493,26 @@
     private void checkAttestationApplicationId(Attestation attestation)
             throws NoSuchAlgorithmException, NameNotFoundException {
         AttestationApplicationId aaid = null;
-        assertNull(attestation.getTeeEnforced().getAttestationApplicationId());
-        aaid = attestation.getSoftwareEnforced().getAttestationApplicationId();
-        assertNotNull(aaid);
-        assertEquals(new AttestationApplicationId(getContext()), aaid);
+        int kmVersion = attestation.getKeymasterVersion();
+        aaid = attestation.getTeeEnforced().getAttestationApplicationId();
+        if (kmVersion >= 3) {
+            // must be TEE enforced and correct
+            assertNotNull(aaid);
+            assertNull(attestation.getSoftwareEnforced().getAttestationApplicationId());
+            assertEquals(new AttestationApplicationId(getContext()), aaid);
+        } else {
+            // may be TEE or SW enforced
+            if (aaid == null) {
+                aaid = attestation.getSoftwareEnforced().getAttestationApplicationId();
+            } else {
+                // if AAID is present in TEE it must not be present in SW.
+                assertNull(attestation.getSoftwareEnforced().getAttestationApplicationId());
+            }
+            // must be correct if present
+            if (aaid != null) {
+                assertEquals(new AttestationApplicationId(getContext()), aaid);
+            }
+        }
     }
 
     private void checkKeyIndependentAttestationInfo(byte[] challenge, int purposes, Date startTime,
@@ -675,6 +691,7 @@
                     break;
 
                 case 2:
+                case 3:
                     assertThat(teeEnforcedDigests, is(expectedDigests));
                     break;
 
@@ -703,7 +720,8 @@
 
     @SuppressWarnings("unchecked")
     private void checkAttestationSecurityLevelDependentParams(Attestation attestation) {
-        assertEquals("Attestation version must be 1", 1, attestation.getAttestationVersion());
+        assertThat("Attestation version must be 1 or 2", attestation.getAttestationVersion(),
+                either(is(1)).or(is(2)));
 
         AuthorizationList teeEnforced = attestation.getTeeEnforced();
         AuthorizationList softwareEnforced = attestation.getSoftwareEnforced();
@@ -716,7 +734,7 @@
                 assertThat("TEE attestation can only come from TEE keymaster",
                         attestation.getKeymasterSecurityLevel(),
                         is(KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT));
-                assertThat(attestation.getKeymasterVersion(), is(2));
+                assertThat(attestation.getKeymasterVersion(), either(is(2)).or(is(3)));
 
                 checkRootOfTrust(attestation);
                 assertThat(teeEnforced.getOsVersion(), is(systemOsVersion));
@@ -729,8 +747,8 @@
                     assertThat("TEE KM version must be 0 or 1 with software attestation",
                             attestation.getKeymasterVersion(), either(is(0)).or(is(1)));
                 } else {
-                    assertThat("Software KM is version 2", attestation.getKeymasterVersion(),
-                            is(2));
+                    assertThat("Software KM is version 3", attestation.getKeymasterVersion(),
+                            is(3));
                     assertThat(softwareEnforced.getOsVersion(), is(systemOsVersion));
                     assertThat(softwareEnforced.getOsPatchLevel(), is(systemPatchLevel));
                 }
diff --git a/tests/tests/location/src/android/location/cts/GnssTtffTests.java b/tests/tests/location/src/android/location/cts/GnssTtffTests.java
index 8c407d5..8b2b62b 100644
--- a/tests/tests/location/src/android/location/cts/GnssTtffTests.java
+++ b/tests/tests/location/src/android/location/cts/GnssTtffTests.java
@@ -3,6 +3,7 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
+import android.os.SystemClock;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 import com.android.compatibility.common.util.CddTest;
@@ -19,8 +20,11 @@
   private static final int STATUS_TO_COLLECT_COUNT = 3;
   private static final int AIDING_DATA_RESET_DELAY_SECS = 10;
   // Threshold values
+  private static final int TTFF_HOT_TH_SECS = 5;
   private static final int TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS = 7;
-  private static final int TTFF_WITH_WIFI_CELLUAR_HOT_TH_SECS = 5;
+  // The worst case we saw in the Nexus 6p device is 15sec,
+  // adding 20% margin to the threshold
+  private static final int TTFF_WITH_WIFI_ONLY_WARM_TH_SECS = 18;
 
   @Override
   protected void setUp() throws Exception {
@@ -29,68 +33,80 @@
   }
 
   /**
-   * Test the TTFF in the case where there is a network connection for both
-   * warm and hot start TTFF cases.
+   * Test the TTFF in the case where there is a network connection for both warm and hot start TTFF
+   * cases.
+   * We first test the "WARM" start where different TTFF thresholds are chosen based on network
+   * connection (cellular vs Wifi). Then we test the "HOT" start where the type of network
+   * connection should not matter hence one threshold is used.
    * @throws Exception
    */
   @CddTest(requirement="7.3.3")
   public void testTtffWithNetwork() throws Exception {
-    checkTtffWarmWithCellularAndWifiOn();
-    checkTtffHotWithCellularAndWifiOn();
+    ensureNetworkStatus();
+    if (hasCellularData()) {
+      checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS);
+    }
+    else {
+      checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_ONLY_WARM_TH_SECS);
+    }
+    checkTtffHotWithWifiOn(TTFF_HOT_TH_SECS);
   }
 
   /**
    * Test Scenario 1
-   * check whether TTFF is below the threshold on the warm start, without any network
-   * 1) Turn on wifi, turn on mobile data
+   * Check whether TTFF is below the threshold on the warm start with Wifi ON
+   * 1) Delete the aiding data.
    * 2) Get GPS, check the TTFF value
+   * @param threshold, the threshold for the TTFF value
    */
-  public void checkTtffWarmWithCellularAndWifiOn() throws Exception {
-    ensureNetworkStatus();
+  private void checkTtffWarmWithWifiOn(long threshold) throws Exception {
     SoftAssert softAssert = new SoftAssert(TAG);
     mTestLocationManager.sendExtraCommand("delete_aiding_data");
     Thread.sleep(TimeUnit.SECONDS.toMillis(AIDING_DATA_RESET_DELAY_SECS));
-    checkTtffByThreshold("checkTtffWarmWithCellularAndWifiOn",
-        TimeUnit.SECONDS.toMillis(TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS), softAssert);
+    checkTtffByThreshold("checkTtffWarmWithWifiOn",
+        TimeUnit.SECONDS.toMillis(threshold), softAssert);
     softAssert.assertAll();
   }
 
   /**
    * Test Scenario 2
-   * check whether TTFF is below the threhold on the hot start, without any network
-   * 1) Turn on wifi, turn on mobile data
-   * 2) Get GPS, check the TTFF value
+   * Check whether TTFF is below the threhold on the hot start with wifi ON
+   * TODO(tccyp): to test the hot case with network connection off
+   * @param threshold, the threshold for the TTFF value
    */
-  public void checkTtffHotWithCellularAndWifiOn() throws Exception {
-    ensureNetworkStatus();
+  private void checkTtffHotWithWifiOn(long threshold) throws Exception {
     SoftAssert softAssert = new SoftAssert(TAG);
-    checkTtffByThreshold("checkTtffHotWithCellularAndWifiOn",
-        TimeUnit.SECONDS.toMillis(TTFF_WITH_WIFI_CELLUAR_HOT_TH_SECS), softAssert);
+    checkTtffByThreshold("checkTtffHotWithWifiOn",
+        TimeUnit.SECONDS.toMillis(threshold), softAssert);
     softAssert.assertAll();
-
   }
 
   /**
-   * Make sure the device has mobile data and wifi connection
+   * Make sure the device has either wifi data or cellular connection
    */
   private void ensureNetworkStatus(){
-    assertTrue("Device has to connect to Wifi to complete this test.",
-        isConnectedToWifi(getContext()));
-    ConnectivityManager connManager = getConnectivityManager(getContext());
-    NetworkInfo mobileNetworkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
-    // check whether the mobile data is ON if the device has cellular capability
-    if (mobileNetworkInfo != null) {
-      TelephonyManager telephonyManager = (TelephonyManager) getContext().getApplicationContext()
-          .getSystemService(getContext().TELEPHONY_SERVICE);
-      assertTrue("Device has to have mobile data ON to complete this test.",
-          telephonyManager.isDataEnabled());
-    }
-    else {
-      Log.i(TAG, "This is a wifi only device.");
-    }
+    assertTrue("Device has to connect to Wifi or Cellular to complete this test.",
+        isConnectedToWifiOrCellular(getContext()));
 
   }
 
+  private boolean hasCellularData() {
+    ConnectivityManager connManager = getConnectivityManager(getContext());
+    NetworkInfo cellularNetworkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+    // check whether the cellular data is ON if the device has cellular capability
+    if (cellularNetworkInfo == null) {
+      Log.i(TAG, "This is a wifi only device.");
+      return false;
+    }
+    TelephonyManager telephonyManager = (TelephonyManager) getContext().getApplicationContext()
+        .getSystemService(getContext().TELEPHONY_SERVICE);
+    if (!telephonyManager.isDataEnabled()) {
+      Log.i(TAG, "Device doesn't have cellular data.");
+      return false;
+    }
+    return true;
+  }
+
   /*
    * Check whether TTFF is below the threshold
    * @param testName
@@ -112,9 +128,9 @@
     mTestLocationManager.requestLocationUpdates(locationListener);
 
 
-    long startTimeMillis = System.currentTimeMillis();
+    long startTimeMillis = SystemClock.elapsedRealtime();
     boolean success = testGnssStatusCallback.awaitTtff();
-    long ttffTimeMillis = System.currentTimeMillis() - startTimeMillis;
+    long ttffTimeMillis = SystemClock.elapsedRealtime() - startTimeMillis;
 
     SoftAssert.failOrWarning(isMeasurementTestStrict(),
             "Test case:" + testName
@@ -129,16 +145,17 @@
   }
 
   /**
-   * Returns whether the device is currently connected to a wifi.
+   * Returns whether the device is currently connected to a wifi or cellular.
    *
    * @param context {@link Context} object
-   * @return {@code true} if connected to Wifi; {@code false} otherwise
+   * @return {@code true} if connected to Wifi or Cellular; {@code false} otherwise
    */
-  public static boolean isConnectedToWifi(Context context) {
+  public static boolean isConnectedToWifiOrCellular(Context context) {
     NetworkInfo info = getActiveNetworkInfo(context);
     return info != null
         && info.isConnected()
-        && info.getType() == ConnectivityManager.TYPE_WIFI;
+        && (info.getType() == ConnectivityManager.TYPE_WIFI
+                 || info.getType() == ConnectivityManager.TYPE_MOBILE);
   }
 
   /**
diff --git a/tests/tests/media/assets/360pvp9decodertest.webm b/tests/tests/media/assets/360pvp9decodertest.webm
deleted file mode 100644
index e35e7a8..0000000
--- a/tests/tests/media/assets/360pvp9decodertest.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/480ph264decodertest.mp4 b/tests/tests/media/assets/480ph264decodertest.mp4
deleted file mode 100644
index 9b7e894..0000000
--- a/tests/tests/media/assets/480ph264decodertest.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4
new file mode 100644
index 0000000..d66b238
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4
new file mode 100644
index 0000000..e46a7c7
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4
new file mode 100644
index 0000000..8af2cf1
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_136x240_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_136x240_30fps.mp4
new file mode 100644
index 0000000..92815cb
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_136x240_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4
new file mode 100644
index 0000000..ec81fe7
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4
new file mode 100644
index 0000000..97e4958
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4
new file mode 100644
index 0000000..6e22847
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4
new file mode 100644
index 0000000..9c061e0
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_192x144_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_192x144_30fps.mp4
new file mode 100644
index 0000000..58d899d
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_192x144_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_202x360_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_202x360_30fps.mp4
new file mode 100644
index 0000000..ade1d8e
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_202x360_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4
new file mode 100644
index 0000000..fc5a7b82
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4
new file mode 100644
index 0000000..52d2466
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x108_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x108_30fps.mp4
new file mode 100644
index 0000000..55b2a50
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x108_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x144_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x144_30fps.mp4
new file mode 100644
index 0000000..34bef63
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x144_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_270x480_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_270x480_30fps.mp4
new file mode 100644
index 0000000..dd8ff39
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_270x480_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4
new file mode 100644
index 0000000..2437833
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_320x240_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_320x240_30fps.mp4
new file mode 100644
index 0000000..1e249f3
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_320x240_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4
new file mode 100644
index 0000000..7d52627
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4
new file mode 100644
index 0000000..a44925b
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_406x720_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_406x720_30fps.mp4
new file mode 100644
index 0000000..8ca7660
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_406x720_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x182_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x182_30fps.mp4
new file mode 100644
index 0000000..e190f9f
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x182_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x240_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x240_30fps.mp4
new file mode 100644
index 0000000..01f33de
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x240_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_480x360_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_480x360_30fps.mp4
new file mode 100644
index 0000000..2163aebe
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_480x360_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4
new file mode 100644
index 0000000..5bae22d9
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x272_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x272_30fps.mp4
new file mode 100644
index 0000000..34738b0
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x272_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x360_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x360_30fps.mp4
new file mode 100644
index 0000000..257037c
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x360_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x480_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x480_30fps.mp4
new file mode 100644
index 0000000..6caa288
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x480_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4
new file mode 100644
index 0000000..081a5d3
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_82x144_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_82x144_30fps.mp4
new file mode 100644
index 0000000..48463db
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_82x144_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x362_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x362_30fps.mp4
new file mode 100644
index 0000000..bfe1fe0
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x362_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x480_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x480_30fps.mp4
new file mode 100644
index 0000000..97df960
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x480_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_960x720_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_960x720_30fps.mp4
new file mode 100644
index 0000000..671329e
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_960x720_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm
new file mode 100644
index 0000000..3e987e0
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm
new file mode 100644
index 0000000..4453d53
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm
new file mode 100644
index 0000000..086a0ee
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_136x240_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_136x240_30fps.webm
new file mode 100644
index 0000000..6b54d00
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_136x240_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm
new file mode 100644
index 0000000..edf8a42
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm
new file mode 100644
index 0000000..b8515e5
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm
new file mode 100644
index 0000000..6c6f219
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm
new file mode 100644
index 0000000..1870a54
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_192x144_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_192x144_30fps.webm
new file mode 100644
index 0000000..45dcd2b
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_192x144_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_202x360_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_202x360_30fps.webm
new file mode 100644
index 0000000..74e4e89
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_202x360_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm
new file mode 100644
index 0000000..20eef7d
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm
new file mode 100644
index 0000000..2108a75
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x108_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x108_30fps.webm
new file mode 100644
index 0000000..2242a44
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x108_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x144_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x144_30fps.webm
new file mode 100644
index 0000000..48962e8
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x144_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_270x480_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_270x480_30fps.webm
new file mode 100644
index 0000000..121e955
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_270x480_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm
new file mode 100644
index 0000000..e95fc45
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_320x240_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_320x240_30fps.webm
new file mode 100644
index 0000000..e105985
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_320x240_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm
new file mode 100644
index 0000000..a3ade36
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm
new file mode 100644
index 0000000..e94d4da
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_406x720_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_406x720_30fps.webm
new file mode 100644
index 0000000..7b903de
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_406x720_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x182_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x182_30fps.webm
new file mode 100644
index 0000000..a37299c
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x182_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x240_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x240_30fps.webm
new file mode 100644
index 0000000..85974a8
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x240_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_480x360_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_480x360_30fps.webm
new file mode 100644
index 0000000..efb8747ff
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_480x360_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm
new file mode 100644
index 0000000..af82abf
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x272_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x272_30fps.webm
new file mode 100644
index 0000000..e8ff426
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x272_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x360_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x360_30fps.webm
new file mode 100644
index 0000000..bb40cd8
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x360_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x480_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x480_30fps.webm
new file mode 100644
index 0000000..d252278
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x480_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm
new file mode 100644
index 0000000..853981a
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_82x144_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_82x144_30fps.webm
new file mode 100644
index 0000000..b6dc9a5
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_82x144_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x362_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x362_30fps.webm
new file mode 100644
index 0000000..cbc6b2cc
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x362_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x480_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x480_30fps.webm
new file mode 100644
index 0000000..adef5bc
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x480_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_960x720_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_960x720_30fps.webm
new file mode 100644
index 0000000..44c0b82
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_960x720_30fps.webm
Binary files differ
diff --git a/tests/tests/media/assets/520x360h264decodertest.mp4 b/tests/tests/media/assets/video_decode_with_cropping-h264_520x360_60fps.mp4
similarity index 100%
rename from tests/tests/media/assets/520x360h264decodertest.mp4
rename to tests/tests/media/assets/video_decode_with_cropping-h264_520x360_60fps.mp4
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_with_cropping-vp9_520x360_60fps.webm b/tests/tests/media/assets/video_decode_with_cropping-vp9_520x360_60fps.webm
new file mode 100644
index 0000000..2ad27ed
--- /dev/null
+++ b/tests/tests/media/assets/video_decode_with_cropping-vp9_520x360_60fps.webm
Binary files differ
diff --git a/tests/tests/media/res/layout/test_runner_activity.xml b/tests/tests/media/res/layout/test_runner_activity.xml
index 8cc88ea..b8ddcf75 100644
--- a/tests/tests/media/res/layout/test_runner_activity.xml
+++ b/tests/tests/media/res/layout/test_runner_activity.xml
@@ -15,24 +15,20 @@
  * limitations under the License.
  -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical">
         <HorizontalScrollView
-            android:layout_width="fill_parent"
-            android:layout_height="250dp">
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
                 <ScrollView
                     android:layout_width="wrap_content"
-                    android:layout_height="fill_parent">
+                    android:layout_height="match_parent">
                         <RelativeLayout
                             android:id="@+id/attach_view"
-                            android:layout_width="fill_parent"
+                            android:layout_width="match_parent"
                             android:layout_height="wrap_content"
                             android:layout_gravity="start"/>
                 </ScrollView>
         </HorizontalScrollView>
-        <ListView
-            android:id="@+id/test_case_list"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
 </LinearLayout>
diff --git a/tests/tests/media/res/raw/h264decodertestgolden.png b/tests/tests/media/res/raw/h264decodertestgolden.png
deleted file mode 100644
index bfaea26..0000000
--- a/tests/tests/media/res/raw/h264decodertestgolden.png
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1216x2160_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1216x2160_golden.png
new file mode 100644
index 0000000..5df519f
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1216x2160_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x544_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x544_golden.png
new file mode 100644
index 0000000..6140266
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x544_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x720_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x720_golden.png
new file mode 100644
index 0000000..03fa8bf
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x720_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_136x240_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_136x240_golden.png
new file mode 100644
index 0000000..20ed587
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_136x240_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1440x1080_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1440x1080_golden.png
new file mode 100644
index 0000000..8365a9c
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1440x1080_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1080_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1080_golden.png
new file mode 100644
index 0000000..a144793
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1080_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1440_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1440_golden.png
new file mode 100644
index 0000000..700613d
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1440_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x818_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x818_golden.png
new file mode 100644
index 0000000..e31fef6
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x818_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_192x144_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_192x144_golden.png
new file mode 100644
index 0000000..d7e75d8
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_192x144_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_202x360_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_202x360_golden.png
new file mode 100644
index 0000000..7f559d2
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_202x360_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1090_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1090_golden.png
new file mode 100644
index 0000000..96a584c
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1090_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1440_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1440_golden.png
new file mode 100644
index 0000000..ab92ea1
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1440_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x108_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x108_golden.png
new file mode 100644
index 0000000..23ff373
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x108_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x144_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x144_golden.png
new file mode 100644
index 0000000..583f878
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x144_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_270x480_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_270x480_golden.png
new file mode 100644
index 0000000..9fc67bd
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_270x480_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2880x2160_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2880x2160_golden.png
new file mode 100644
index 0000000..bc1068b4
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2880x2160_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_320x240_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_320x240_golden.png
new file mode 100644
index 0000000..e0ce44c
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_320x240_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x1634_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x1634_golden.png
new file mode 100644
index 0000000..5a6b2ac
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x1634_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x2160_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x2160_golden.png
new file mode 100644
index 0000000..ced5dab
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x2160_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_406x720_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_406x720_golden.png
new file mode 100644
index 0000000..a7ebdb9
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_406x720_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x182_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x182_golden.png
new file mode 100644
index 0000000..1abb1b8
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x182_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x240_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x240_golden.png
new file mode 100644
index 0000000..5c466b8
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x240_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_480x360_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_480x360_golden.png
new file mode 100644
index 0000000..49008d8
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_480x360_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_608x1080_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_608x1080_golden.png
new file mode 100644
index 0000000..78a54a3
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_608x1080_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x272_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x272_golden.png
new file mode 100644
index 0000000..33e00d2
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x272_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x360_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x360_golden.png
new file mode 100644
index 0000000..6261051
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x360_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x480_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x480_golden.png
new file mode 100644
index 0000000..3d66bd9
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x480_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_810x1440_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_810x1440_golden.png
new file mode 100644
index 0000000..9991fe9
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_810x1440_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_82x144_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_82x144_golden.png
new file mode 100644
index 0000000..df23e4c
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_82x144_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x362_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x362_golden.png
new file mode 100644
index 0000000..02266ee
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x362_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x480_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x480_golden.png
new file mode 100644
index 0000000..d86f05b
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x480_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_960x720_golden.png b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_960x720_golden.png
new file mode 100644
index 0000000..964723d
--- /dev/null
+++ b/tests/tests/media/res/raw/video_decode_accuracy_and_capability_960x720_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/h264decodertest520x360golden.png b/tests/tests/media/res/raw/video_decode_with_cropping_520x360_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/h264decodertest520x360golden.png
rename to tests/tests/media/res/raw/video_decode_with_cropping_520x360_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/h264decodertest520x360original.png b/tests/tests/media/res/raw/video_decode_with_cropping_520x360_original.png
similarity index 100%
rename from tests/tests/media/res/raw/h264decodertest520x360original.png
rename to tests/tests/media/res/raw/video_decode_with_cropping_520x360_original.png
Binary files differ
diff --git a/tests/tests/media/res/raw/vp9decodertestgolden.png b/tests/tests/media/res/raw/vp9decodertestgolden.png
deleted file mode 100644
index cad6778..0000000
--- a/tests/tests/media/res/raw/vp9decodertestgolden.png
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/CodecUtils.java b/tests/tests/media/src/android/media/cts/CodecUtils.java
index 1097f41..24c1174 100644
--- a/tests/tests/media/src/android/media/cts/CodecUtils.java
+++ b/tests/tests/media/src/android/media/cts/CodecUtils.java
@@ -18,10 +18,10 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Color;
-import android.graphics.ImageFormat;
 import android.graphics.Rect;
 import android.media.cts.CodecImage;
 import android.media.Image;
+import android.media.MediaCodec.BufferInfo;
 import android.os.Environment;
 import android.util.Log;
 
@@ -31,7 +31,6 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
-import java.security.MessageDigest;
 
 public class CodecUtils  {
     private static final String TAG = "CodecUtils";
@@ -151,86 +150,6 @@
 
     public native static float[] Raw2YUVStats(long[] rawStats);
 
-    public static String getImageMD5Checksum(Image image) throws Exception {
-        int format = image.getFormat();
-        if (ImageFormat.YUV_420_888 != format) {
-            Log.w(TAG, "unsupported image format");
-            return "";
-        }
-
-        MessageDigest md = MessageDigest.getInstance("MD5");
-
-        int imageWidth = image.getWidth();
-        int imageHeight = image.getHeight();
-
-        Image.Plane[] planes = image.getPlanes();
-        for (int i = 0; i < planes.length; ++i) {
-            ByteBuffer buf = planes[i].getBuffer();
-
-            int width, height, rowStride, pixelStride, x, y;
-            rowStride = planes[i].getRowStride();
-            pixelStride = planes[i].getPixelStride();
-            if (i == 0) {
-                width = imageWidth;
-                height = imageHeight;
-            } else {
-                width = imageWidth / 2;
-                height = imageHeight /2;
-            }
-            // local contiguous pixel buffer
-            byte[] bb = new byte[width * height];
-            if (buf.hasArray()) {
-                byte b[] = buf.array();
-                int offs = buf.arrayOffset();
-                if (pixelStride == 1) {
-                    for (y = 0; y < height; ++y) {
-                        System.arraycopy(bb, y * width, b, y * rowStride + offs, width);
-                    }
-                } else {
-                    // do it pixel-by-pixel
-                    for (y = 0; y < height; ++y) {
-                        int lineOffset = offs + y * rowStride;
-                        for (x = 0; x < width; ++x) {
-                            bb[y * width + x] = b[lineOffset + x * pixelStride];
-                        }
-                    }
-                }
-            } else { // almost always ends up here due to direct buffers
-                int pos = buf.position();
-                if (pixelStride == 1) {
-                    for (y = 0; y < height; ++y) {
-                        buf.position(pos + y * rowStride);
-                        buf.get(bb, y * width, width);
-                    }
-                } else {
-                    // local line buffer
-                    byte[] lb = new byte[rowStride];
-                    // do it pixel-by-pixel
-                    for (y = 0; y < height; ++y) {
-                        buf.position(pos + y * rowStride);
-                        // we're only guaranteed to have pixelStride * (width - 1) + 1 bytes
-                        buf.get(lb, 0, pixelStride * (width - 1) + 1);
-                        for (x = 0; x < width; ++x) {
-                            bb[y * width + x] = lb[x * pixelStride];
-                        }
-                    }
-                }
-                buf.position(pos);
-            }
-            md.update(bb, 0, width * height);
-        }
-
-        return convertByteArrayToHEXString(md.digest());
-    }
-
-    private static String convertByteArrayToHEXString(byte[] ba) throws Exception {
-        StringBuilder result = new StringBuilder();
-        for (int i = 0; i < ba.length; i++) {
-            result.append(Integer.toString((ba[i] & 0xff) + 0x100, 16).substring(1));
-        }
-        return result.toString();
-    }
-
     /**
      * This method reads the binarybar code on the top row of a bitmap. Each 16x16
      * block is one digit, with black=0 and white=1. LSB is on the left.
diff --git a/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java b/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
index bbbbb3f..010d992 100644
--- a/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
+++ b/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
@@ -15,42 +15,127 @@
  */
 package android.media.cts;
 
+import static junit.framework.TestCase.assertTrue;
+
+import static org.junit.Assert.fail;
+
 import android.media.cts.R;
 
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.media.MediaFormat;
-import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 import android.view.View;
 
 import com.android.compatibility.common.util.MediaUtils;
 
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.rules.Timeout;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.junit.Test;
 
 @TargetApi(24)
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class DecodeAccuracyTest extends DecodeAccuracyTestBase {
 
     private static final String TAG = DecodeAccuracyTest.class.getSimpleName();
-    private static final String H264_VIDEO_FILE_NAME = "480ph264decodertest.mp4";
-    private static final String VP9_VIDEO_FILE_NAME = "360pvp9decodertest.webm";
-    private static final String H264_CROPPED_VIDEO_FILE_NAME = "520x360h264decodertest.mp4";
+    private static final Field[] fields = R.raw.class.getFields();
     private static final int ALLOWED_GREATEST_PIXEL_DIFFERENCE = 90;
     private static final int OFFSET = 10;
-    private static final int PER_TEST_TIMEOUT_S = 30;
+    private static final long PER_TEST_TIMEOUT_MS = 60000;
+    private static final String[] VIDEO_FILES = {
+        // 144p
+        "video_decode_accuracy_and_capability-h264_256x108_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_256x144_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_192x144_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_82x144_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_256x108_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_256x144_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_192x144_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_82x144_30fps.webm",
+        // 240p
+        "video_decode_accuracy_and_capability-h264_426x182_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_426x240_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_320x240_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_136x240_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_426x182_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_426x240_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_320x240_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_136x240_30fps.webm",
+        // 360p
+        "video_decode_accuracy_and_capability-h264_640x272_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_640x360_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_480x360_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_202x360_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_640x272_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_640x360_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_480x360_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_202x360_30fps.webm",
+        // 480p
+        "video_decode_accuracy_and_capability-h264_854x362_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_854x480_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_640x480_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_270x480_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_854x362_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_854x480_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_640x480_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_270x480_30fps.webm",
+        // 720p
+        "video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_960x720_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_406x720_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_960x720_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_406x720_30fps.webm",
+        // 1080p
+        "video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm",
+        // 1440p
+        "video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm",
+        // 2160p
+        "video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm",
+        // cropped
+        "video_decode_with_cropping-h264_520x360_60fps.mp4",
+        "video_decode_with_cropping-vp9_520x360_60fps.webm"
+    };
 
     private View videoView;
     private VideoViewFactory videoViewFactory;
-
-    @Rule
-    public Timeout globalTimeout = Timeout.seconds(PER_TEST_TIMEOUT_S);
+    private String fileName;
 
     @After
     @Override
@@ -64,131 +149,61 @@
         super.tearDown();
     }
 
-    /* <------------- Tests Using H264 -------------> */
-    @Test
-    public void testH264GLViewVideoDecode() throws Exception {
-        runH264DecodeAccuracyTest(
-                new GLSurfaceViewFactory(),
-                new VideoFormat(H264_VIDEO_FILE_NAME));
-    }
-
-    @Test
-    public void testH264GLViewLargerHeightVideoDecode() throws Exception {
-        runH264DecodeAccuracyTest(
-                new GLSurfaceViewFactory(),
-                getLargerHeightVideoFormat(new VideoFormat(H264_VIDEO_FILE_NAME)));
-    }
-
-    @Test
-    public void testH264GLViewLargerWidthVideoDecode() throws Exception {
-        runH264DecodeAccuracyTest(
-                new GLSurfaceViewFactory(),
-                getLargerWidthVideoFormat(new VideoFormat(H264_VIDEO_FILE_NAME)));
-    }
-
-    @Test
-    public void testH264SurfaceViewVideoDecode() throws Exception {
-        runH264DecodeAccuracyTest(
-                new SurfaceViewFactory(),
-                new VideoFormat(H264_VIDEO_FILE_NAME));
-    }
-
-    @Test
-    public void testH264SurfaceViewLargerHeightVideoDecode() throws Exception {
-        runH264DecodeAccuracyTest(
-                new SurfaceViewFactory(),
-                getLargerHeightVideoFormat(new VideoFormat(H264_VIDEO_FILE_NAME)));
-    }
-
-    @Test
-    public void testH264SurfaceViewLargerWidthVideoDecode() throws Exception {
-        runH264DecodeAccuracyTest(
-                new SurfaceViewFactory(),
-                getLargerWidthVideoFormat(new VideoFormat(H264_VIDEO_FILE_NAME)));
-    }
-
-    /* <------------- Tests Using VP9 -------------> */
-    @Test
-    public void testVP9GLViewVideoDecode() throws Exception {
-        runVP9DecodeAccuracyTest(
-                new GLSurfaceViewFactory(),
-                new VideoFormat(VP9_VIDEO_FILE_NAME));
-    }
-
-    @Test
-    public void testVP9GLViewLargerHeightVideoDecode() throws Exception {
-        runVP9DecodeAccuracyTest(
-                new GLSurfaceViewFactory(),
-                getLargerHeightVideoFormat(new VideoFormat(VP9_VIDEO_FILE_NAME)));
-    }
-
-    @Test
-    public void testVP9GLViewLargerWidthVideoDecode() throws Exception {
-        runVP9DecodeAccuracyTest(
-                new GLSurfaceViewFactory(),
-                getLargerWidthVideoFormat(new VideoFormat(VP9_VIDEO_FILE_NAME)));
-    }
-
-    @Test
-    public void testVP9SurfaceViewVideoDecode() throws Exception {
-        runVP9DecodeAccuracyTest(
-                new SurfaceViewFactory(),
-                new VideoFormat(VP9_VIDEO_FILE_NAME));
-    }
-
-    @Test
-    public void testVP9SurfaceViewLargerHeightVideoDecode() throws Exception {
-        runVP9DecodeAccuracyTest(
-                new SurfaceViewFactory(),
-                getLargerHeightVideoFormat(new VideoFormat(VP9_VIDEO_FILE_NAME)));
-    }
-
-    @Test
-    public void testVP9SurfaceViewLargerWidthVideoDecode() throws Exception {
-        runVP9DecodeAccuracyTest(
-                new SurfaceViewFactory(),
-                getLargerWidthVideoFormat(new VideoFormat(VP9_VIDEO_FILE_NAME)));
-    }
-
-    /* <------------- Tests H264 with cropping -------------> */
-    @Test
-    public void testH264GLViewCroppedVideoDecode() throws Exception {
-        runH264DecodeCroppedTest(
-                new GLSurfaceViewFactory(),
-                new VideoFormat(H264_CROPPED_VIDEO_FILE_NAME));
-    }
-
-    @Test
-    public void testH264SurfaceViewCroppedVideoDecode() throws Exception {
-        runH264DecodeCroppedTest(
-                new SurfaceViewFactory(),
-                new VideoFormat(H264_CROPPED_VIDEO_FILE_NAME));
-    }
-
-    private void runH264DecodeAccuracyTest(
-            VideoViewFactory videoViewFactory, VideoFormat videoFormat) {
-        if (MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            runDecodeAccuracyTest(videoViewFactory, videoFormat, R.raw.h264decodertestgolden);
+    @Parameters(name = "{0}")
+    public static Collection<Object[]> data() {
+        final List<Object[]> testParams = new ArrayList<>();
+        for (int i = 0; i < VIDEO_FILES.length; i++) {
+            final String file = VIDEO_FILES[i];
+            Pattern regex = Pattern.compile("^\\w+-(\\w+)_\\d+fps.\\w+");
+            Matcher matcher = regex.matcher(file);
+            String testName = "";
+            if (matcher.matches()) {
+                testName = matcher.group(1);
+            }
+            testParams.add(new Object[] { testName.replace("_", " ").toUpperCase(), file });
         }
+        return testParams;
     }
 
-    private void runVP9DecodeAccuracyTest(
-            VideoViewFactory videoViewFactory, VideoFormat videoFormat) {
-        if (MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-            runDecodeAccuracyTest(videoViewFactory, videoFormat, R.raw.vp9decodertestgolden);
+    public DecodeAccuracyTest(String testname, String fileName) {
+        this.fileName = fileName;
+    }
+
+    @Test(timeout=PER_TEST_TIMEOUT_MS)
+    public void testGLViewDecodeAccuracy() throws Exception {
+        runTest(new GLSurfaceViewFactory(), new VideoFormat(fileName));
+    }
+
+    @Test(timeout=PER_TEST_TIMEOUT_MS)
+    public void testGLViewLargerHeightDecodeAccuracy() throws Exception {
+        runTest(new GLSurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)));
+    }
+
+    @Test(timeout=PER_TEST_TIMEOUT_MS)
+    public void testGLViewLargerWidthDecodeAccuracy() throws Exception {
+        runTest(new GLSurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)));
+    }
+
+    @Test(timeout=PER_TEST_TIMEOUT_MS)
+    public void testSurfaceViewVideoDecodeAccuracy() throws Exception {
+        runTest(new SurfaceViewFactory(), new VideoFormat(fileName));
+    }
+
+    @Test(timeout=PER_TEST_TIMEOUT_MS)
+    public void testSurfaceViewLargerHeightDecodeAccuracy() throws Exception {
+        runTest(new SurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)));
+    }
+
+    @Test(timeout=PER_TEST_TIMEOUT_MS)
+    public void testSurfaceViewLargerWidthDecodeAccuracy() throws Exception {
+        runTest(new SurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)));
+    }
+
+    private void runTest(VideoViewFactory videoViewFactory, VideoFormat vf) {
+        if (!MediaUtils.canDecodeVideo(vf.getMimeType(), vf.getWidth(), vf.getHeight(), 30)) {
+            MediaUtils.skipTest(TAG, "No supported codec is found.");
+            return;
         }
-    }
-
-    private void runH264DecodeCroppedTest(
-            VideoViewFactory videoViewFactory, VideoFormat videoFormat) {
-        if (MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            runDecodeAccuracyTest(videoViewFactory, videoFormat, R.raw.h264decodertest520x360golden);
-        }
-    }
-
-    private void runDecodeAccuracyTest(
-            VideoViewFactory videoViewFactory, VideoFormat videoFormat, int goldenResId) {
-        checkNotNull(videoFormat);
         this.videoViewFactory = checkNotNull(videoViewFactory);
         this.videoView = videoViewFactory.createView(getHelper().getContext());
         final int maxRetries = 3;
@@ -210,39 +225,37 @@
                 }
             }
         }
-        // In the case of SurfaceView, VideoViewSnapshot can only capture incoming frames,
-        // so it needs to be created before start decoding.
+        final int golden = getGoldenId(vf.getDescription(), vf.getOriginalSize());
+        assertTrue("No golden found.", golden != 0);
         final VideoViewSnapshot videoViewSnapshot = videoViewFactory.getVideoViewSnapshot();
-        decodeVideo(videoFormat, videoViewFactory);
-        validateResult(videoFormat, videoViewSnapshot, goldenResId);
+        decodeVideo(vf, videoViewFactory);
+        validateResult(vf, videoViewSnapshot, golden);
     }
 
     private void decodeVideo(VideoFormat videoFormat, VideoViewFactory videoViewFactory) {
         final SimplePlayer player = new SimplePlayer(getHelper().getContext());
         final SimplePlayer.PlayerResult playerResult = player.decodeVideoFrames(
                 videoViewFactory.getSurface(), videoFormat, 10);
-        assertTrue("Failed to configure video decoder.", playerResult.isConfigureSuccess());
-        assertTrue("Failed to start video decoder.", playerResult.isStartSuccess());
-        assertTrue("Failed to decode the video.", playerResult.isSuccess());
+        assertTrue(playerResult.getFailureMessage(), playerResult.isSuccess());
     }
 
     private void validateResult(
-            VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenResId) {
+            VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenId) {
         final Bitmap result = checkNotNull("The expected bitmap from snapshot is null",
                 getHelper().generateBitmapFromVideoViewSnapshot(videoViewSnapshot));
-        final Bitmap golden = getHelper().generateBitmapFromImageResourceId(goldenResId);
+        final Bitmap golden = getHelper().generateBitmapFromImageResourceId(goldenId);
         final BitmapCompare.Difference difference = BitmapCompare.computeMinimumDifference(
                 result, golden, videoFormat.getOriginalWidth(), videoFormat.getOriginalHeight());
         assertTrue("With the best matched border crop ("
-                    + difference.bestMatchBorderCrop.first + ", "
-                    + difference.bestMatchBorderCrop.second + "), "
-                    + "greatest pixel difference is "
-                    + difference.greatestPixelDifference
-                    + (difference.greatestPixelDifferenceCoordinates != null
-                    ? " at (" + difference.greatestPixelDifferenceCoordinates.first + ", "
-                    + difference.greatestPixelDifferenceCoordinates.second + ")" : "")
-                    + " which is over the allowed difference " + ALLOWED_GREATEST_PIXEL_DIFFERENCE,
-                    difference.greatestPixelDifference <= ALLOWED_GREATEST_PIXEL_DIFFERENCE);
+                + difference.bestMatchBorderCrop.first + ", "
+                + difference.bestMatchBorderCrop.second + "), "
+                + "greatest pixel difference is "
+                + difference.greatestPixelDifference
+                + (difference.greatestPixelDifferenceCoordinates != null
+                        ? " at (" + difference.greatestPixelDifferenceCoordinates.first + ", "
+                            + difference.greatestPixelDifferenceCoordinates.second + ")" : "")
+                + " which is over the allowed difference " + ALLOWED_GREATEST_PIXEL_DIFFERENCE,
+                difference.greatestPixelDifference <= ALLOWED_GREATEST_PIXEL_DIFFERENCE);
     }
 
     private static VideoFormat getLargerHeightVideoFormat(VideoFormat videoFormat) {
@@ -279,4 +292,22 @@
         };
     }
 
+    /**
+     * Returns the resource id by matching parts of the video and golden file name.
+     */
+    private static int getGoldenId(String description, String size) {
+        for (Field field : fields) {
+            try {
+                final String name = field.getName();
+                if (name.contains("golden") && name.contains(description) && name.contains(size)) {
+                    int id = field.getInt(null);
+                    return field.getInt(null);
+                }
+            } catch (IllegalAccessException | NullPointerException e) {
+                // No file found.
+            }
+        }
+        return 0;
+    }
+
 }
diff --git a/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java b/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java
index 680ade5..0e92e3d 100644
--- a/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java
+++ b/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java
@@ -17,13 +17,17 @@
 
 import android.media.cts.R;
 
+import static org.junit.Assert.assertNotNull;
+
 import com.android.compatibility.common.util.ApiLevelUtil;
 
 import android.annotation.TargetApi;
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.res.AssetFileDescriptor;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -34,6 +38,7 @@
 import android.media.MediaCodec;
 import android.media.MediaCodec.BufferInfo;
 import android.media.MediaCodec.CodecException;
+import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.net.Uri;
@@ -46,9 +51,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.test.rule.ActivityTestRule;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -63,13 +66,12 @@
 import android.widget.RelativeLayout;
 
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import java.util.concurrent.TimeUnit;
+import java.util.HashMap;
 
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
@@ -79,40 +81,34 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.runner.RunWith;
+import org.junit.Rule;
 
 @TargetApi(16)
-@RunWith(AndroidJUnit4.class)
-public class DecodeAccuracyTestBase
-    extends ActivityInstrumentationTestCase2<DecodeAccuracyTestActivity> {
+public class DecodeAccuracyTestBase {
 
     protected Context mContext;
     protected Resources mResources;
     protected DecodeAccuracyTestActivity mActivity;
     protected TestHelper testHelper;
 
-    public DecodeAccuracyTestBase() {
-        super(DecodeAccuracyTestActivity.class);
-    }
+    @Rule
+    public ActivityTestRule<DecodeAccuracyTestActivity> mActivityRule =
+            new ActivityTestRule<>(DecodeAccuracyTestActivity.class);
 
     @Before
-    @Override
     public void setUp() throws Exception {
-        super.setUp();
-        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
-        setActivityInitialTouchMode(false);
-        mActivity = getActivity();
-        getInstrumentation().waitForIdleSync();
-        mContext = getInstrumentation().getTargetContext();
-        mResources = mContext.getResources();
+        mActivity = mActivityRule.getActivity();
+        mContext = mActivity.getApplicationContext();
+        mResources = mActivity.getResources();
         testHelper = new TestHelper(mContext, mActivity);
     }
 
     @After
-    @Override
     public void tearDown() throws Exception {
         mActivity = null;
-        super.tearDown();
+        mResources = null;
+        mContext = null;
+        mActivityRule = null;
     }
 
     protected void bringActivityToFront() {
@@ -135,42 +131,58 @@
         return reference;
     }
 
-    public static class SimplePlayer {
+    /* Simple Player that decodes a local video file only. */
+    @TargetApi(16)
+    static class SimplePlayer {
 
-        public static final long DECODE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1) / 2;
+        public static final long MIN_MS_PER_FRAME = TimeUnit.SECONDS.toMillis(1) / 10; // 10 FPS
+        public static final int END_OF_STREAM = -1;
+        public static final int DEQUEUE_SUCCESS = 1;
+        public static final int DEQUEUE_FAIL = 0;
 
+        private static final String TAG = SimplePlayer.class.getSimpleName();
         private static final int NO_TRACK_INDEX = -3;
         private static final long DEQUEUE_TIMEOUT_US = 20;
-        private static final String TAG = SimplePlayer.class.getSimpleName();
 
         private final Context context;
         private final MediaExtractor extractor;
+        private final String codecName;
         private MediaCodec decoder;
+        private byte[] outputBytes;
+        private boolean renderToSurface;
+        private MediaCodecList mediaCodecList;
+        private Surface surface;
 
         public SimplePlayer(Context context) {
-            this(context, new MediaExtractor());
+            this(context, null);
         }
 
-        public SimplePlayer(Context context, MediaExtractor extractor) {
+        public SimplePlayer(Context context, String codecName) {
             this.context = checkNotNull(context);
-            this.extractor = checkNotNull(extractor);
+            this.codecName = codecName;
+            this.extractor = new MediaExtractor();
+            this.renderToSurface = false;
+            this.surface = null;
         }
 
-        /*
-         * The function play the corresponding file for certain number of frames,
+        /**
+         * The function play the corresponding file for certain number of frames.
          *
          * @param surface is the surface view of decoder output.
          * @param videoFormat is the format of the video to extract and decode.
-         * @param numOfTotalFrame is the number of Frame wish to play.
-         * @return a PlayerResult object indicating success or failure.
+         * @param numOfTotalFrames is the number of Frame wish to play.
+         * @param msPerFrameCap is the maximum msec per frame. No cap is set if value is less than 1.
+         * @return {@link PlayerResult} that consists the result.
          */
         public PlayerResult decodeVideoFrames(
-                Surface surface, VideoFormat videoFormat, int numOfTotalFrames) {
+                Surface surface, VideoFormat videoFormat, int numOfTotalFrames, long msPerFrameCap) {
+            this.surface = surface;
             PlayerResult playerResult;
-            if (prepare(surface, videoFormat)) {
+            if (prepareVideoDecode(videoFormat)) {
                 if (startDecoder()) {
-                    playerResult = decodeFramesAndDisplay(
-                            surface, numOfTotalFrames, numOfTotalFrames * DECODE_TIMEOUT_MS);
+                    final long timeout =
+                            Math.max(MIN_MS_PER_FRAME, msPerFrameCap) * numOfTotalFrames * 2;
+                    playerResult = decodeFramesAndPlay(numOfTotalFrames, timeout, msPerFrameCap);
                 } else {
                     playerResult = PlayerResult.failToStart();
                 }
@@ -181,88 +193,177 @@
             return new PlayerResult(playerResult);
         }
 
-        public PlayerResult decodeVideoFrames(VideoFormat videoFormat, int numOfTotalFrames) {
-            return decodeVideoFrames(null, videoFormat, numOfTotalFrames);
+        public PlayerResult decodeVideoFrames(
+                Surface surface, VideoFormat videoFormat, int numOfTotalFrames) {
+            return decodeVideoFrames(surface, videoFormat, numOfTotalFrames, 0);
         }
 
-        /*
-         * The function set up the extractor and decoder with proper format.
-         * This must be called before decodeFramesAndDisplay.
+        public PlayerResult decodeVideoFrames(VideoFormat videoFormat, int numOfTotalFrames) {
+            return decodeVideoFrames(null, videoFormat, numOfTotalFrames, 0);
+        }
+
+        /**
+         * The function sets up the extractor and video decoder with proper format.
+         * This must be called before doing starting up the decoder.
          */
-        private boolean prepare(Surface surface, VideoFormat videoFormat) {
-            Log.i(TAG, "Preparing to decode the media file.");
-            if (!setExtractorDataSource(videoFormat)) {
+        private boolean prepareVideoDecode(VideoFormat videoFormat) {
+            MediaFormat mediaFormat = prepareExtractor(videoFormat);
+            if (mediaFormat == null) {
                 return false;
             }
-            int trackNum = getFirstVideoTrackIndex(extractor);
+            configureVideoFormat(mediaFormat, videoFormat);
+            setRenderToSurface(surface != null);
+            return createDecoder(mediaFormat) && configureDecoder(surface, mediaFormat);
+        }
+
+        /**
+         * Sets up the extractor and gets the {@link MediaFormat} of the track.
+         */
+        private MediaFormat prepareExtractor(VideoFormat videoFormat) {
+            if (!setExtractorDataSource(videoFormat)) {
+                return null;
+            }
+            final int trackNum = getFirstTrackIndexByType(videoFormat.getMediaFormat());
             if (trackNum == NO_TRACK_INDEX) {
-                return false;
+                return null;
             }
             extractor.selectTrack(trackNum);
-            MediaFormat mediaFormat = extractor.getTrackFormat(trackNum);
-            configureFormat(mediaFormat, videoFormat);
-            return configureDecoder(surface, mediaFormat);
+            return extractor.getTrackFormat(trackNum);
         }
 
-        /* The function decode video frames and display in a surface. */
-        private PlayerResult decodeFramesAndDisplay(
-                Surface surface, int numOfTotalFrames, long timeOutMs) {
-            Log.i(TAG, "Starting decoding.");
-            checkNotNull(decoder);
+        /**
+         * The function decode video frames and display in a surface.
+         *
+         * @param numOfTotalFrames is the number of frames to be decoded.
+         * @param timeOutMs is the time limit for decoding the frames.
+         * @param msPerFrameCap is the maximum msec per frame. No cap is set if value is less than 1.
+         * @return {@link PlayerResult} that consists the result.
+         */
+        private PlayerResult decodeFramesAndPlay(
+                int numOfTotalFrames, long timeOutMs, long msPerFrameCap) {
             int numOfDecodedFrames = 0;
-            long decodeStart = 0;
-            boolean renderToSurface = surface != null ? true : false;
-            BufferInfo info = new BufferInfo();
-            ByteBuffer inputBuffer;
-            ByteBuffer[] inputBufferArray = decoder.getInputBuffers();
-            long loopStart = SystemClock.elapsedRealtime();
+            long firstOutputTimeMs = 0;
+            long lastFrameAt = 0;
+            final long loopStart = SystemClock.elapsedRealtime();
 
             while (numOfDecodedFrames < numOfTotalFrames
                     && (SystemClock.elapsedRealtime() - loopStart < timeOutMs)) {
                 try {
-                    int inputBufferIndex = decoder.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
-                    if (inputBufferIndex >= 0) {
-                        if (ApiLevelUtil.isBefore(Build.VERSION_CODES.LOLLIPOP)) {
-                            inputBuffer = inputBufferArray[inputBufferIndex];
-                        } else {
-                            inputBuffer = decoder.getInputBuffer(inputBufferIndex);
-                        }
-                        if (decodeStart == 0) {
-                            decodeStart = SystemClock.elapsedRealtime();
-                        }
-                        int sampleSize = extractor.readSampleData(inputBuffer, 0);
-                        if (sampleSize > 0) {
-                            decoder.queueInputBuffer(
-                                    inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
-                            extractor.advance();
-                        }
-                    }
-                    int decoderStatus = decoder.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT_US);
-                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    queueDecoderInputBuffer();
+                } catch (IllegalStateException exception) {
+                    Log.e(TAG, "IllegalStateException in queueDecoderInputBuffer", exception);
+                    break;
+                }
+                try {
+                    final int outputResult = dequeueDecoderOutputBuffer();
+                    if (outputResult == SimplePlayer.END_OF_STREAM) {
                         break;
                     }
-                    if (decoderStatus >= 0 && info.size > 0) {
-                        decoder.releaseOutputBuffer(decoderStatus, renderToSurface);
+                    if (outputResult == SimplePlayer.DEQUEUE_SUCCESS) {
+                        if (firstOutputTimeMs == 0) {
+                            firstOutputTimeMs = SystemClock.elapsedRealtime();
+                        }
+                        if (msPerFrameCap > 0) {
+                            // Slow down if cap is set and not reached.
+                            final long delayMs =
+                                    msPerFrameCap - (SystemClock.elapsedRealtime() - lastFrameAt);
+                            if (lastFrameAt != 0 && delayMs > 0) {
+                                final long threadDelayMs = 3; // In case of delay in thread.
+                                if (delayMs > threadDelayMs) {
+                                    try {
+                                        Thread.sleep(delayMs - threadDelayMs);
+                                    } catch (InterruptedException ex) { /* */}
+                                }
+                                while (SystemClock.elapsedRealtime() - lastFrameAt
+                                        < msPerFrameCap) { /* */ }
+                            }
+                            lastFrameAt = SystemClock.elapsedRealtime();
+                        }
                         numOfDecodedFrames++;
                     }
                 } catch (IllegalStateException exception) {
-                    Log.e(TAG, "IllegalStateException in decodeFramesAndDisplay", exception);
-                    break;
+                    Log.e(TAG, "IllegalStateException in dequeueDecoderOutputBuffer", exception);
                 }
             }
-            long totalTime = SystemClock.elapsedRealtime() - decodeStart;
-            Log.i(TAG, "Finishing decoding.");
+            final long totalTime = SystemClock.elapsedRealtime() - firstOutputTimeMs;
             return new PlayerResult(true, true, numOfTotalFrames == numOfDecodedFrames, totalTime);
         }
 
+        /**
+         * Queues the input buffer with the media file one buffer at a time.
+         *
+         * @return true if success, fail otherwise.
+         */
+        private boolean queueDecoderInputBuffer() {
+            ByteBuffer inputBuffer;
+            final ByteBuffer[] inputBufferArray = decoder.getInputBuffers();
+            final int inputBufferIndex = decoder.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
+            if (inputBufferIndex >= 0) {
+                if (ApiLevelUtil.isBefore(Build.VERSION_CODES.LOLLIPOP)) {
+                    inputBuffer = inputBufferArray[inputBufferIndex];
+                } else {
+                    inputBuffer = decoder.getInputBuffer(inputBufferIndex);
+                }
+                final int sampleSize = extractor.readSampleData(inputBuffer, 0);
+                if (sampleSize > 0) {
+                    decoder.queueInputBuffer(
+                            inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
+                    extractor.advance();
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Dequeues the output buffer.
+         * For video decoder, renders to surface if provided.
+         * For audio decoder, gets the bytes from the output buffer.
+         *
+         * @return an integer indicating its status (fail, success, or end of stream).
+         */
+        private int dequeueDecoderOutputBuffer() {
+            final BufferInfo info = new BufferInfo();
+            final int decoderStatus = decoder.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT_US);
+            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                return END_OF_STREAM;
+            }
+            if (decoderStatus >= 0) {
+                // For JELLY_BEAN_MR2- devices, when rendering to a surface,
+                // info.size seems to always return 0 even if
+                // the decoder successfully decoded the frame.
+                if (info.size <= 0 && ApiLevelUtil.isAtLeast(Build.VERSION_CODES.JELLY_BEAN_MR2)) {
+                    return DEQUEUE_FAIL;
+                }
+                if (!renderToSurface) {
+                    ByteBuffer outputBuffer;
+                    if (ApiLevelUtil.isBefore(Build.VERSION_CODES.LOLLIPOP)) {
+                        outputBuffer = decoder.getOutputBuffers()[decoderStatus];
+                    } else {
+                        outputBuffer = decoder.getOutputBuffer(decoderStatus);
+                    }
+                    outputBytes = new byte[info.size];
+                    outputBuffer.get(outputBytes);
+                    outputBuffer.clear();
+                }
+                decoder.releaseOutputBuffer(decoderStatus, renderToSurface);
+                return DEQUEUE_SUCCESS;
+            }
+            return DEQUEUE_FAIL;
+        }
+
         private void release() {
             decoderRelease();
             extractorRelease();
         }
 
         private boolean setExtractorDataSource(VideoFormat videoFormat) {
+            checkNotNull(videoFormat);
             try {
-                extractor.setDataSource(context, videoFormat.loadUri(context), null);
+                final AssetFileDescriptor afd = videoFormat.getAssetFileDescriptor(context);
+                extractor.setDataSource(
+                        afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+                afd.close();
             } catch (IOException exception) {
                 Log.e(TAG, "IOException in setDataSource", exception);
                 return false;
@@ -270,20 +371,50 @@
             return true;
         }
 
+        /**
+         * Creates a decoder based on conditions.
+         *
+         * <p>If codec name is provided, {@link MediaCodec#createByCodecName(String)} is used.
+         * If codec name is not provided, {@link MediaCodecList#findDecoderForFormat(MediaFormat)}
+         * is preferred on LOLLIPOP and up for finding out the codec name that
+         * supports the media format.
+         * For OS older than LOLLIPOP, {@link MediaCodec#createDecoderByType(String)} is used.
+         */
+        private boolean createDecoder(MediaFormat mediaFormat) {
+            try {
+                if (codecName != null) {
+                    decoder = MediaCodec.createByCodecName(codecName);
+                } else if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
+                    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
+                        // On LOLLIPOP, format must not contain a frame rate.
+                        mediaFormat.setString(MediaFormat.KEY_FRAME_RATE, null);
+                    }
+                    if (mediaCodecList == null) {
+                        mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+                    }
+                    decoder = MediaCodec.createByCodecName(
+                            mediaCodecList.findDecoderForFormat(mediaFormat));
+                } else {
+                    decoder = MediaCodec.createDecoderByType(
+                            mediaFormat.getString(MediaFormat.KEY_MIME));
+                }
+            } catch (Exception exception) {
+                Log.e(TAG, "Exception during decoder creation", exception);
+                decoderRelease();
+                return false;
+            }
+            return true;
+        }
+
         private boolean configureDecoder(Surface surface, MediaFormat mediaFormat) {
             try {
-                decoder = MediaCodec.createDecoderByType(
-                        mediaFormat.getString(MediaFormat.KEY_MIME));
                 decoder.configure(mediaFormat, surface, null, 0);
             } catch (Exception exception) {
-                if (exception instanceof IOException) {
-                    Log.e(TAG, "IOException in createDecoderByType", exception);
-                } else if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.LOLLIPOP)
-                        && exception instanceof CodecException) {
-                    Log.e(TAG, "CodecException in createDecoderByType", exception);
+                Log.e(TAG, "Exception during decoder configuration", exception);
+                try {
                     decoder.reset();
-                } else {
-                    Log.e(TAG, "Unknown exception in createDecoderByType", exception);
+                } catch (Exception resetException) {
+                    Log.e(TAG, "Exception during decoder reset", resetException);
                 }
                 decoderRelease();
                 return false;
@@ -291,19 +422,16 @@
             return true;
         }
 
+        private void setRenderToSurface(boolean render) {
+            this.renderToSurface = render;
+        }
+
         private boolean startDecoder() {
             try {
                 decoder.start();
             } catch (Exception exception) {
-                if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.LOLLIPOP)
-                        && exception instanceof CodecException) {
-                    Log.e(TAG, "CodecException in startDecoder", exception);
-                    decoder.reset();
-                } else if (exception instanceof IllegalStateException) {
-                    Log.e(TAG, "IllegalStateException in startDecoder", exception);
-                } else {
-                    Log.e(TAG, "Unknown exception in startDecoder", exception);
-                }
+                Log.e(TAG, "Exception during decoder start", exception);
+                decoder.reset();
                 decoderRelease();
                 return false;
             }
@@ -317,16 +445,17 @@
             try {
                 decoder.stop();
             } catch (IllegalStateException exception) {
+                decoder.reset();
                 // IllegalStateException happens when decoder fail to start.
-                Log.e(TAG, "IllegalStateException in decoder stop", exception);
+                Log.e(TAG, "IllegalStateException during decoder stop", exception);
             } finally {
                 try {
                     decoder.release();
                 } catch (IllegalStateException exception) {
-                    Log.e(TAG, "IllegalStateException in decoder release", exception);
+                    Log.e(TAG, "IllegalStateException during decoder release", exception);
                 }
+                decoder = null;
             }
-            decoder = null;
         }
 
         private void extractorRelease() {
@@ -336,11 +465,11 @@
             try {
                 extractor.release();
             } catch (IllegalStateException exception) {
-                Log.e(TAG, "IllegalStateException in extractor release", exception);
+                Log.e(TAG, "IllegalStateException during extractor release", exception);
             }
         }
 
-        private static void configureFormat(MediaFormat mediaFormat, VideoFormat videoFormat) {
+        private static void configureVideoFormat(MediaFormat mediaFormat, VideoFormat videoFormat) {
             checkNotNull(mediaFormat);
             checkNotNull(videoFormat);
             videoFormat.setMimeType(mediaFormat.getString(MediaFormat.KEY_MIME));
@@ -348,32 +477,33 @@
             videoFormat.setHeight(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT));
             mediaFormat.setInteger(MediaFormat.KEY_WIDTH, videoFormat.getWidth());
             mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, videoFormat.getHeight());
-
-            if (videoFormat.getMaxWidth() != VideoFormat.UNSET
-                    && videoFormat.getMaxHeight() != VideoFormat.UNSET) {
+            if (ApiLevelUtil.isBefore(Build.VERSION_CODES.KITKAT)) {
+                return;
+            }
+            if (videoFormat.getMaxWidth() != VideoFormat.INT_UNSET
+                && videoFormat.getMaxHeight() != VideoFormat.INT_UNSET) {
                 mediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, videoFormat.getMaxWidth());
                 mediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, videoFormat.getMaxHeight());
             }
         }
 
-        /*
-         * The function returns the first video track found.
-         *
-         * @param extractor is the media extractor instantiated with a video uri.
-         * @return the index of the first video track if found, NO_TRACK_INDEX otherwise.
+        /**
+         * The function returns the first track found based on the media type.
          */
-        private static int getFirstVideoTrackIndex(MediaExtractor extractor) {
+        private int getFirstTrackIndexByType(String format) {
             for (int i = 0; i < extractor.getTrackCount(); i++) {
                 MediaFormat trackMediaFormat = extractor.getTrackFormat(i);
-                if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(format + "/")) {
                     return i;
                 }
             }
-            Log.e(TAG, "couldn't get a video track");
+            Log.e(TAG, "couldn't get a " + format + " track");
             return NO_TRACK_INDEX;
         }
 
-        /* Stores the result from SimplePlayer. */
+        /**
+         * Stores the result from SimplePlayer.
+         */
         public static final class PlayerResult {
 
             public static final int UNSET = -1;
@@ -405,31 +535,30 @@
                 return new PlayerResult(true, false, false, UNSET);
             }
 
+            public String getFailureMessage() {
+                if (!configureSuccess) {
+                    return "Failed to configure decoder.";
+                } else if (!startSuccess) {
+                    return "Failed to start decoder.";
+                } else if (!decodeSuccess) {
+                    return "Failed to decode the expected number of frames.";
+                } else {
+                    return "Failed to finish decoding.";
+                }
+            }
+
             public boolean isConfigureSuccess() {
                 return configureSuccess;
             }
 
-            public boolean isStartSuccess() {
-                return startSuccess;
-            }
-
-            public boolean isDecodeSuccess() {
-                return decodeSuccess;
-            }
-
             public boolean isSuccess() {
-                return isConfigureSuccess() && isStartSuccess()
-                        && isDecodeSuccess() && getTotalTime() != UNSET;
+                return configureSuccess && startSuccess && decodeSuccess && getTotalTime() != UNSET;
             }
 
             public long getTotalTime() {
                 return totalTime;
             }
 
-            public boolean isFailureForAll() {
-                return (!isConfigureSuccess() && !isStartSuccess()
-                        && !isDecodeSuccess() && getTotalTime() == UNSET);
-            }
         }
 
     }
@@ -493,7 +622,7 @@
         }
 
         public synchronized Bitmap generateBitmapFromVideoViewSnapshot(VideoViewSnapshot snapshot) {
-            final long timeOutMs = TimeUnit.SECONDS.toMillis(10);
+            final long timeOutMs = TimeUnit.SECONDS.toMillis(30);
             final long start = SystemClock.elapsedRealtime();
             handler.post(snapshot);
             try {
@@ -731,7 +860,7 @@
 
     @Override
     public Surface getSurface() {
-        return surfaceHolder.getSurface();
+        return surfaceHolder == null ? null : surfaceHolder.getSurface();
     }
 
     @Override
@@ -845,11 +974,11 @@
         private float[] textureTransform = new float[16];
 
         private float[] triangleVerticesData = {
-                // X, Y, Z, U, V
-                -1f, -1f,  0f,  0f,  1f,
-                 1f, -1f,  0f,  1f,  1f,
-                -1f,  1f,  0f,  0f,  0f,
-                 1f,  1f,  0f,  1f,  0f,
+            // X, Y, Z, U, V
+            -1f, -1f,  0f,  0f,  1f,
+             1f, -1f,  0f,  1f,  1f,
+            -1f,  1f,  0f,  0f,  0f,
+             1f,  1f,  0f,  1f,  0f,
         };
         // Make the top-left corner corresponds to texture coordinate
         // (0, 0). This complies with the transformation matrix obtained from
@@ -886,15 +1015,17 @@
         private Surface surface = null;
         private SurfaceTexture surfaceTexture;
         private ByteBuffer byteBuffer;
+        private Looper looper;
 
         public GLSurfaceViewThread() {}
 
         @Override
         public void run() {
             Looper.prepare();
+            looper = Looper.myLooper();
             triangleVertices = ByteBuffer
                     .allocateDirect(triangleVerticesData.length * FLOAT_SIZE_BYTES)
-                            .order(ByteOrder.nativeOrder()).asFloatBuffer();
+                    .order(ByteOrder.nativeOrder()).asFloatBuffer();
             triangleVertices.put(triangleVerticesData).position(0);
 
             eglSetup();
@@ -936,13 +1067,13 @@
             }
             // Configure EGL for pbuffer and OpenGL ES 2.0, 24-bit RGB.
             int[] configAttribs = {
-                    EGL10.EGL_RED_SIZE, 8,
-                    EGL10.EGL_GREEN_SIZE, 8,
-                    EGL10.EGL_BLUE_SIZE, 8,
-                    EGL10.EGL_ALPHA_SIZE, 8,
-                    EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
-                    EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
-                    EGL10.EGL_NONE
+                EGL10.EGL_RED_SIZE, 8,
+                EGL10.EGL_GREEN_SIZE, 8,
+                EGL10.EGL_BLUE_SIZE, 8,
+                EGL10.EGL_ALPHA_SIZE, 8,
+                EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+                EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
+                EGL10.EGL_NONE
             };
             EGLConfig[] configs = new EGLConfig[1];
             int[] numConfigs = new int[1];
@@ -952,8 +1083,8 @@
             }
             // Configure EGL context for OpenGL ES 2.0.
             int[] contextAttribs = {
-                    EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
-                    EGL10.EGL_NONE
+                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+                EGL10.EGL_NONE
             };
             eglContext = egl10.eglCreateContext(
                     eglDisplay, configs[0], EGL10.EGL_NO_CONTEXT, contextAttribs);
@@ -963,9 +1094,9 @@
             }
             // Create a pbuffer surface.
             int[] surfaceAttribs = {
-                    EGL10.EGL_WIDTH, VIEW_WIDTH,
-                    EGL10.EGL_HEIGHT, VIEW_HEIGHT,
-                    EGL10.EGL_NONE
+                EGL10.EGL_WIDTH, VIEW_WIDTH,
+                EGL10.EGL_HEIGHT, VIEW_HEIGHT,
+                EGL10.EGL_NONE
             };
             eglSurface = egl10.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs);
             checkEglError("eglCreatePbufferSurface");
@@ -975,6 +1106,7 @@
         }
 
         public void release() {
+            looper.quit();
             if (eglDisplay != EGL10.EGL_NO_DISPLAY) {
                 egl10.eglMakeCurrent(eglDisplay,
                         EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
@@ -987,6 +1119,8 @@
             eglSurface = EGL10.EGL_NO_SURFACE;
             surface.release();
             surfaceTexture.release();
+            byteBufferIsReady = false;
+            byteBuffer =  null;
         }
 
         /* Makes our EGL context and surface current. */
@@ -1050,8 +1184,7 @@
             GLES20.glEnableVertexAttribArray(aTextureHandle);
             checkGlError("glEnableVertexAttribArray aTextureHandle");
 
-            GLES20.glUniformMatrix4fv(uTextureTransformHandle, 1, false,
-                    textureTransform, 0);
+            GLES20.glUniformMatrix4fv(uTextureTransformHandle, 1, false, textureTransform, 0);
             checkGlError("glUniformMatrix uTextureTransformHandle");
 
             GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
@@ -1193,25 +1326,25 @@
 class SurfaceViewSnapshot extends VideoViewSnapshot  {
 
     private static final String TAG = SurfaceViewSnapshot.class.getSimpleName();
-    private static final int PIXELCOPY_REQUEST_SLEEP_MS = 100;
+    private static final int PIXELCOPY_REQUEST_SLEEP_MS = 30;
     private static final int PIXELCOPY_TIMEOUT_MS = 1000;
 
-    private final Thread copyThread;
+    private Thread copyThread;
+    private SynchronousPixelCopy copyHelper;
     private Bitmap bitmap;
     private int copyResult;
 
     public SurfaceViewSnapshot(final SurfaceView surfaceView, final int width, final int height) {
-        this.copyResult = -1;
         this.copyThread = new Thread(new Runnable() {
             @Override
             public void run() {
-                SynchronousPixelCopy copyHelper = new SynchronousPixelCopy();
+                copyHelper = new SynchronousPixelCopy();
                 bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
                 try {
                     // Wait for SurfaceView to be available.
-                    while (copyResult != PixelCopy.SUCCESS) {
+                    while ((copyResult = copyHelper.request(surfaceView, bitmap))
+                            != PixelCopy.SUCCESS) {
                         Thread.sleep(PIXELCOPY_REQUEST_SLEEP_MS);
-                        copyResult = copyHelper.request(surfaceView, bitmap);
                     }
                 } catch (InterruptedException e) {
                     Log.e(TAG, "Pixel Copy is stopped/interrupted before it finishes.", e);
@@ -1240,6 +1373,12 @@
         if (copyThread.isAlive()) {
             copyThread.interrupt();
         }
+        copyThread = null;
+        if (copyHelper != null) {
+            copyHelper.release();
+            copyHelper = null;
+        }
+        bitmap = null;
     }
 
     private static class SynchronousPixelCopy implements OnPixelCopyFinishedListener {
@@ -1256,7 +1395,9 @@
         }
 
         public void release() {
-            thread.quit();
+            if (thread.isAlive()) {
+                thread.quit();
+            }
         }
 
         public int request(SurfaceView source, Bitmap dest) {
@@ -1299,7 +1440,7 @@
 
     private static final String TAG = GLSurfaceViewSnapshot.class.getSimpleName();
     private static final int GET_BYTEBUFFER_SLEEP_MS = 30;
-    private static final int GET_BYTEBUFFER_MAX_ATTEMPTS = 20;
+    private static final int GET_BYTEBUFFER_MAX_ATTEMPTS = 30;
 
     private final GLSurfaceViewFactory glSurfaceViewFactory;
     private final int width;
@@ -1318,16 +1459,22 @@
     public synchronized void run() {
         try {
             waitForByteBuffer();
-        } catch (InterruptedException e) {
-            Log.e(TAG, e.getMessage());
+        } catch (InterruptedException exception) {
+            Log.e(TAG, exception.getMessage());
             bitmap = null;
             return;
         }
-        ByteBuffer byteBuffer = glSurfaceViewFactory.getByteBuffer();
-        bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        byteBuffer.rewind();
-        bitmap.copyPixelsFromBuffer(byteBuffer);
-        bitmapIsReady = true;
+        try {
+            final ByteBuffer byteBuffer = glSurfaceViewFactory.getByteBuffer();
+            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            byteBuffer.rewind();
+            bitmap.copyPixelsFromBuffer(byteBuffer);
+            bitmapIsReady = true;
+            byteBuffer.clear();
+        } catch (NullPointerException exception) {
+            Log.e(TAG, "glSurfaceViewFactory or byteBuffer may have been released", exception);
+            bitmap = null;
+        }
     }
 
     @Override
@@ -1353,50 +1500,38 @@
 
 }
 
-/* Stores information of a video. */
+/* Stores information of a video file. */
 class VideoFormat {
 
-    public static final int UNSET = -1;
-    public static final String MIMETYPE_UNSET = "UNSET";
-    public static final String MIMETYPE_KEY = "mimeType";
-    public static final String WIDTH_KEY = "width";
-    public static final String HEIGHT_KEY = "height";
-    public static final String FRAMERATE_KEY = "frameRate";
+    public static final String STRING_UNSET = "UNSET";
+    public static final int INT_UNSET = -1;
 
     private final String filename;
-    private Uri uri;
-    private String mimeType = MIMETYPE_UNSET;
-    private int width = UNSET;
-    private int height = UNSET;
-    private int maxWidth = UNSET;
-    private int maxHeight = UNSET;
-    private int originalWidth = UNSET;
-    private int originalHeight = UNSET;
 
-    public VideoFormat(String filename, Uri uri) {
-        this.filename = filename;
-        this.uri = uri;
-    }
+    private String mimeType = STRING_UNSET;
+    private int width = INT_UNSET;
+    private int height = INT_UNSET;
+    private int maxWidth = INT_UNSET;
+    private int maxHeight = INT_UNSET;
+    private FilenameParser filenameParser;
 
     public VideoFormat(String filename) {
-        this(filename, null);
+        this.filename = filename;
     }
 
     public VideoFormat(VideoFormat videoFormat) {
-        this(videoFormat.filename, videoFormat.uri);
+        this(videoFormat.filename);
     }
 
-    public Uri loadUri(Context context) {
-        uri = createCacheFile(context);
-        return uri;
+    private FilenameParser getParsedName() {
+        if (filenameParser == null) {
+            filenameParser = new FilenameParser(filename);
+        }
+        return filenameParser;
     }
 
-    public Uri getUri() {
-        return uri;
-    }
-
-    public String getFilename() {
-        return filename;
+    public String getMediaFormat() {
+        return "video";
     }
 
     public void setMimeType(String mimeType) {
@@ -1404,14 +1539,14 @@
     }
 
     public String getMimeType() {
+        if (mimeType.equals(STRING_UNSET)) {
+            return getParsedName().getMimeType();
+        }
         return mimeType;
     }
 
     public void setWidth(int width) {
         this.width = width;
-        if (this.originalWidth == UNSET) {
-            this.originalWidth = width;
-        }
     }
 
     public void setMaxWidth(int maxWidth) {
@@ -1419,6 +1554,9 @@
     }
 
     public int getWidth() {
+        if (width == INT_UNSET) {
+            return getParsedName().getWidth();
+        }
         return width;
     }
 
@@ -1427,14 +1565,11 @@
     }
 
     public int getOriginalWidth() {
-        return originalWidth;
+        return getParsedName().getWidth();
     }
 
     public void setHeight(int height) {
         this.height = height;
-        if (this.originalHeight == UNSET) {
-            this.originalHeight = height;
-        }
     }
 
     public void setMaxHeight(int maxHeight) {
@@ -1442,6 +1577,9 @@
     }
 
     public int getHeight() {
+        if (height == INT_UNSET) {
+            return getParsedName().getHeight();
+        }
         return height;
     }
 
@@ -1450,28 +1588,28 @@
     }
 
     public int getOriginalHeight() {
-        return originalHeight;
+        return getParsedName().getHeight();
     }
 
-    private Uri createCacheFile(Context context) {
-        try {
-            File cacheFile = new File(context.getCacheDir(), filename);
-            if (cacheFile.createNewFile() == false) {
-                cacheFile.delete();
-                cacheFile.createNewFile();
-            }
-            InputStream inputStream = context.getAssets().open(filename);
-            FileOutputStream fileOutputStream = new FileOutputStream(cacheFile);
-            final int bufferSize = 1024 * 512;
-            byte[] buffer = new byte[bufferSize];
+    public String getOriginalSize() {
+        if (width == INT_UNSET || height == INT_UNSET) {
+            return getParsedName().getSize();
+        }
+        return width + "x" + height;
+    }
 
-            while (inputStream.read(buffer) != -1) {
-                fileOutputStream.write(buffer, 0, bufferSize);
-            }
-            fileOutputStream.close();
-            inputStream.close();
-            return Uri.fromFile(cacheFile);
-        } catch (IOException e) {
+    public String getDescription() {
+        return getParsedName().getDescription();
+    }
+
+    public String toPrettyString() {
+        return getParsedName().toPrettyString();
+    }
+
+    public AssetFileDescriptor getAssetFileDescriptor(Context context) {
+        try {
+            return context.getAssets().openFd(filename);
+        } catch (Exception e) {
             e.printStackTrace();
             return null;
         }
@@ -1479,6 +1617,77 @@
 
 }
 
+/* File parser for filenames with format of {description}-{mimeType}_{size}_{framerate}.{format} */
+class FilenameParser {
+
+    static final String VP9 = "vp9";
+    static final String H264 = "h264";
+
+    private final String filename;
+
+    private String codec = VideoFormat.STRING_UNSET;
+    private String description = VideoFormat.STRING_UNSET;
+    private int width = VideoFormat.INT_UNSET;
+    private int height = VideoFormat.INT_UNSET;
+
+    FilenameParser(String filename) {
+        this.filename = filename;
+        parseFilename(filename);
+    }
+
+    public String getCodec() {
+        return codec;
+    }
+
+    public String getMimeType() {
+        switch (codec) {
+            case H264:
+                return MimeTypes.VIDEO_H264;
+            case VP9:
+                return MimeTypes.VIDEO_VP9;
+            default:
+                return null;
+        }
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public String getSize() {
+        return width + "x" + height;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    String toPrettyString() {
+        if (codec != null) {
+            return codec.toUpperCase() + " " + getSize();
+        }
+        return filename;
+    }
+
+    private void parseFilename(String filename) {
+        final String descriptionDelimiter = "-";
+        final String infoDelimiter = "_";
+        final String sizeDelimiter = "x";
+        try {
+            this.description = filename.split(descriptionDelimiter)[0];
+            final String[] fileInfo = filename.split(descriptionDelimiter)[1].split(infoDelimiter);
+            this.codec = fileInfo[0];
+            this.width = Integer.parseInt(fileInfo[1].split(sizeDelimiter)[0]);
+            this.height = Integer.parseInt(fileInfo[1].split(sizeDelimiter)[1]);
+        } catch (Exception exception) { /* Filename format does not match. */ }
+    }
+
+}
+
 /**
  * Compares bitmaps to determine if they are similar.
  *
@@ -1494,8 +1703,6 @@
     private static final int Y = 1;
     private static final int Z = 2;
 
-    private static SparseArray<double[]> pixelTransformCache = new SparseArray<>();
-
     private BitmapCompare() {}
 
     /**
@@ -1505,15 +1712,14 @@
      * @param bitmap2 A bitmap to compare to bitmap1.
      * @return A {@link Difference} with an integer describing the greatest pixel difference,
      *     using {@link Integer#MAX_VALUE} for completely different bitmaps, and an optional
-     *     {@link Pair<Integer, Integer>} of the (col, row) pixel coordinate
-     *     where it was first found.
+     *     {@link Pair<Integer, Integer>} of the (col, row) pixel coordinate where it was first found.
      */
     @TargetApi(12)
     public static Difference computeDifference(Bitmap bitmap1, Bitmap bitmap2) {
-        if ((bitmap1 == null || bitmap2 == null) && bitmap1 != bitmap2) {
+        if (bitmap1 == null || bitmap2 == null) {
             return new Difference(Integer.MAX_VALUE);
         }
-        if (bitmap1 == bitmap2 || bitmap1.sameAs(bitmap2)) {
+        if (bitmap1.equals(bitmap2) || bitmap1.sameAs(bitmap2)) {
             return new Difference(0);
         }
         if (bitmap1.getHeight() != bitmap2.getHeight() || bitmap1.getWidth() != bitmap2.getWidth()) {
@@ -1537,9 +1743,11 @@
             greatestDifferenceIndex / bitmap1.getHeight()));
     }
 
+    @SuppressLint("UseSparseArrays")
     private static double[][] convertRgbToCieLab(Bitmap bitmap) {
+        final HashMap<Integer, double[]> pixelTransformCache = new HashMap<>();
         final double[][] result = new double[bitmap.getHeight() * bitmap.getWidth()][3];
-        final int pixels[] = new int[bitmap.getHeight() * bitmap.getWidth()];
+        final int[] pixels = new int[bitmap.getHeight() * bitmap.getWidth()];
         bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
         for (int i = 0; i < pixels.length; i++) {
             final double[] transformedColor = pixelTransformCache.get(pixels[i]);
@@ -1584,7 +1792,6 @@
      */
     private static double[] convertRgbToXyz(int rgbColor) {
         final double[] comp = {Color.red(rgbColor), Color.green(rgbColor), Color.blue(rgbColor)};
-
         for (int i = 0; i < comp.length; i++) {
             comp[i] /= 255.0;
             if (comp[i] > 0.04045) {
@@ -1630,7 +1837,6 @@
         comp[X] /= 95.047;
         comp[Y] /= 100.0;
         comp[Z] /= 108.883;
-
         for (int i = 0; i < comp.length; i++) {
             if (comp[i] > 0.008856) {
                 comp[i] = Math.pow(comp[i], (1.0 / 3.0));
@@ -1815,7 +2021,7 @@
             this.greatestPixelDifference = greatestPixelDifference;
             this.greatestPixelDifferenceCoordinates = greatestPixelDifferenceCoordinates;
             this.bestMatchBorderCrop = bestMatchBorderCrop;
-       }
+        }
     }
 
 }
diff --git a/tests/tests/media/src/android/media/cts/DecoderConformanceTest.java b/tests/tests/media/src/android/media/cts/DecoderConformanceTest.java
index ead1201..50a2011 100644
--- a/tests/tests/media/src/android/media/cts/DecoderConformanceTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderConformanceTest.java
@@ -21,8 +21,6 @@
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
-import android.media.cts.CodecUtils;
-import android.media.Image;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaExtractor;
@@ -37,10 +35,8 @@
 import com.android.compatibility.common.util.Stat;
 
 import java.io.BufferedReader;
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.List;
@@ -115,7 +111,7 @@
         return readResourceLines(md5FileName);
     }
 
-    private void releaseMediacodec() {
+    private void release() {
         try {
             mDecoder.stop();
         } catch (Exception e) {
@@ -137,26 +133,16 @@
         int resId = mResources.getIdentifier(vectorName, "raw", mContext.getPackageName());
         AssetFileDescriptor testFd = mResources.openRawResourceFd(resId);
         mExtractor = new MediaExtractor();
-        mExtractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
-                                 testFd.getLength());
+        mExtractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(), testFd.getLength());
         mExtractor.selectTrack(0);
-        int trackIndex = mExtractor.getSampleTrackIndex();
-        MediaFormat format = mExtractor.getTrackFormat(trackIndex);
-        mDecoder = MediaCodec.createByCodecName(decoderName);
 
         MediaCodecInfo codecInfo = mDecoder.getCodecInfo();
         MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
-        if (!caps.isFormatSupported(format)) {
+        if (!caps.isFormatSupported(mExtractor.getTrackFormat(0))) {
             return Status.SKIP;
         }
 
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        int decodeFrameCount = 0;
-        boolean sawInputEOS = false;
-        boolean sawOutputEOS = false;
-        final long kTimeOutUs = 5000; // 5ms timeout
         List<String> frameMD5Sums;
-
         try {
             frameMD5Sums = readVectorMD5Sums(mime, vectorName);
         } catch(Exception e) {
@@ -164,80 +150,16 @@
             return Status.FAIL;
         }
 
-        int expectFrameCount = frameMD5Sums.size();
-        mDecoder.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
-        mDecoder.start();
-
-        while (!sawOutputEOS) {
-            // handle input
-            if (!sawInputEOS) {
-                int inputIndex = mDecoder.dequeueInputBuffer(kTimeOutUs);
-                if (inputIndex >= 0) {
-                    ByteBuffer buffer = mDecoder.getInputBuffer(inputIndex);
-                    int sampleSize = mExtractor.readSampleData(buffer, 0);
-                    if (sampleSize < 0) {
-                        mDecoder.queueInputBuffer(inputIndex, 0, 0, 0,
-                                                  MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                        sawInputEOS = true;
-                    } else {
-                        mDecoder.queueInputBuffer(inputIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
-                        mExtractor.advance();
-                    }
-                }
+        try {
+            mDecoder = MediaCodec.createByCodecName(decoderName);
+            if (MediaUtils.verifyDecoder(mDecoder, mExtractor, frameMD5Sums)) {
+                return Status.PASS;
             }
-
-            // handle output
-            int outputBufIndex = mDecoder.dequeueOutputBuffer(info, kTimeOutUs);
-            if (outputBufIndex >= 0) {
-                if (info.size > 0) { // Disregard 0-sized buffers at the end.
-                    MediaFormat bufferFormat = mDecoder.getOutputFormat(outputBufIndex);
-                    int width = bufferFormat.getInteger(MediaFormat.KEY_WIDTH);
-                    int height = bufferFormat.getInteger(MediaFormat.KEY_HEIGHT);
-                    int colorFmt = bufferFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
-
-                    String md5CheckSum = "";
-                    try  {
-                        Image image = mDecoder.getOutputImage(outputBufIndex);
-                        md5CheckSum = CodecUtils.getImageMD5Checksum(image);
-                    } catch (Exception e) {
-                        Log.e(TAG, "getOutputImage md5CheckSum failed", e);
-                        return Status.FAIL;
-                    }
-
-                    if (!md5CheckSum.equals(frameMD5Sums.get(decodeFrameCount))) {
-                        Log.d(TAG, "Frame " + decodeFrameCount + " md5sum mismatch");
-                        return Status.FAIL;
-                    }
-
-                    decodeFrameCount++;
-                }
-                mDecoder.releaseOutputBuffer(outputBufIndex, false /* render */);
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    sawOutputEOS = true;
-                }
-            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat decOutputFormat = mDecoder.getOutputFormat();
-                int width = decOutputFormat.getInteger(MediaFormat.KEY_WIDTH);
-                int height = decOutputFormat.getInteger(MediaFormat.KEY_HEIGHT);
-                Log.d(TAG, "output format " + decOutputFormat);
-            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                Log.i(TAG, "Skip handling MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED");
-            } else {
-                assertEquals(
-                        "decoder.dequeueOutputBuffer() unrecognized return index: " + outputBufIndex,
-                        MediaCodec.INFO_TRY_AGAIN_LATER, outputBufIndex);
-            }
-        }
-
-        if (decodeFrameCount != expectFrameCount) {
-            Log.d(TAG, vectorName + " decode frame count not match");
+            Log.d(TAG, vectorName + " decoded frames do not match");
             return Status.FAIL;
+        } finally {
+            release();
         }
-
-        mDecoder.stop();
-        mDecoder.release();
-        mExtractor.release();
-        return Status.PASS;
     }
 
     void decodeTestVectors(String mime, boolean isGoog) throws Exception {
@@ -272,7 +194,7 @@
 
                 if (!pass) {
                     // Release mediacodec in failure or exception cases.
-                    releaseMediacodec();
+                    release();
                 }
             }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java
new file mode 100644
index 0000000..cb0feba
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.media.cts;
+
+import android.media.cts.R;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.media.AudioManager;
+import android.media.MediaCodec;
+import android.media.MediaDataSource;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaRecorder;
+import android.media.MediaTimestamp;
+import android.media.PlaybackParams;
+import android.media.SubtitleData;
+import android.media.SyncParams;
+import android.media.TimedText;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.Visualizer;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.support.test.filters.SmallTest;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.UUID;
+import java.util.Vector;
+import java.util.concurrent.CountDownLatch;
+
+import junit.framework.AssertionFailedError;
+
+/**
+ * Tests for the MediaPlayer API and local video/audio playback.
+ *
+ * The files in res/raw used by testLocalVideo* are (c) copyright 2008,
+ * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
+ * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
+ */
+@SmallTest
+@RequiresDevice
+public class MediaPlayerDrmTest extends MediaPlayerDrmTestBase {
+
+    private static final String LOG_TAG = "MediaPlayerDrmTest";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    // Asset helpers
+
+    private static Uri getUriFromFile(String path) {
+        return Uri.fromFile(new File(getDownloadedPath(path)));
+    }
+
+    private static String getDownloadedPath(String fileName) {
+        return getDownloadedFolder() + File.separator + fileName;
+    }
+
+    private static String getDownloadedFolder() {
+        return Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_DOWNLOADS).getPath();
+    }
+
+    private static final class Resolution {
+        public final boolean isHD;
+        public final int width;
+        public final int height;
+
+        public Resolution(boolean isHD, int width, int height) {
+            this.isHD = isHD;
+            this.width = width;
+            this.height = height;
+        }
+    }
+
+    private static final Resolution RES_720P  = new Resolution(true, 1280,  720);
+    private static final Resolution RES_AUDIO = new Resolution(false,   0,    0);
+
+
+    // Assets
+
+    private static final Uri CENC_AUDIO_URL = Uri.parse(
+            "http://yt-dash-mse-test.commondatastorage.googleapis.com" +
+            "/media/car_cenc-20120827-8c.mp4");
+    private static final Uri CENC_AUDIO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-8c.mp4");
+
+    private static final Uri CENC_VIDEO_URL = Uri.parse(
+            "http://yt-dash-mse-test.commondatastorage.googleapis.com" +
+            "/media/car_cenc-20120827-88.mp4");
+    private static final Uri CENC_VIDEO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-88.mp4");
+
+
+    // Tests
+
+    @SmallTest
+    @RequiresDevice
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V0_SYNC() throws Exception {
+        download(CENC_AUDIO_URL,
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V0_SYNC_TEST);
+    }
+
+    @SmallTest
+    @RequiresDevice
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V1_ASYNC() throws Exception {
+        download(CENC_AUDIO_URL,
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V1_ASYNC_TEST);
+    }
+
+    @SmallTest
+    @RequiresDevice
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V2_SYNC_CONFIG() throws Exception {
+        download(CENC_AUDIO_URL,
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V2_SYNC_CONFIG_TEST);
+    }
+
+    @SmallTest
+    @RequiresDevice
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V3_ASYNC_DRMPREPARED() throws Exception {
+        download(CENC_AUDIO_URL,
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V3_ASYNC_DRMPREPARED_TEST);
+    }
+
+    @SmallTest
+    @RequiresDevice
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V5_ASYNC_WITH_HANDLER() throws Exception {
+        download(CENC_AUDIO_URL,
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER);
+    }
+
+    // helpers
+
+    private void stream(Uri uri, Resolution res, ModularDrmTestType testType) throws Exception {
+        playModularDrmVideo(uri, res.width, res.height, testType);
+    }
+
+    private void download(Uri remote, Uri local, Resolution res, ModularDrmTestType testType)
+            throws Exception {
+        playModularDrmVideoDownload(remote, local, res.width, res.height, testType);
+    }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerDrmTestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayerDrmTestBase.java
new file mode 100644
index 0000000..6f5a355
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerDrmTestBase.java
@@ -0,0 +1,1097 @@
+/*
+ * 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.media.cts;
+
+import android.app.DownloadManager;
+import android.app.DownloadManager.Request;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.MediaDrm;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.DrmInfo;
+import android.media.ResourceBusyException;
+import android.media.UnsupportedSchemeException;
+import android.media.cts.MediaPlayerTestBase.Monitor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpCookie;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.Vector;
+import java.util.logging.Logger;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+/**
+ * Base class for tests which use MediaPlayer to play audio or video.
+ */
+public class MediaPlayerDrmTestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> {
+    private static final Logger LOG = Logger.getLogger(MediaPlayerTestBase.class.getName());
+
+    protected static final int STREAM_RETRIES = 3;
+
+    protected Monitor mOnVideoSizeChangedCalled = new Monitor();
+    protected Monitor mOnErrorCalled = new Monitor();
+
+    protected Context mContext;
+    protected Resources mResources;
+
+    protected MediaPlayer mMediaPlayer = null;
+    protected MediaStubActivity mActivity;
+
+    public MediaPlayerDrmTestBase() {
+        super(MediaStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = getActivity();
+        getInstrumentation().waitForIdleSync();
+        try {
+            runTestOnUiThread(new Runnable() {
+                public void run() {
+                    mMediaPlayer = new MediaPlayer();
+                }
+            });
+        } catch (Throwable e) {
+            e.printStackTrace();
+            fail();
+        }
+        mContext = getInstrumentation().getTargetContext();
+        mResources = mContext.getResources();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+        mActivity = null;
+        super.tearDown();
+    }
+
+    protected void setOnErrorListener() {
+        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                mOnErrorCalled.signal();
+                return false;
+            }
+        });
+    }
+
+    private static class PrepareFailedException extends Exception {}
+
+    //////////////////////////////////////////////////////////////////////////////////////////
+    // Modular DRM
+
+    private static final String TAG = "MediaPlayerDrmTestBase";
+
+    protected static final int PLAY_TIME_MS = 60 * 1000;
+    protected byte[] mKeySetId;
+    protected boolean mAudioOnly;
+
+    private static final byte[] CLEAR_KEY_CENC = {
+            (byte)0x1a, (byte)0x8a, (byte)0x20, (byte)0x95,
+            (byte)0xe4, (byte)0xde, (byte)0xb2, (byte)0xd2,
+            (byte)0x9e, (byte)0xc8, (byte)0x16, (byte)0xac,
+            (byte)0x7b, (byte)0xae, (byte)0x20, (byte)0x82
+            };
+
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+
+    final byte[] CLEARKEY_PSSH = hexStringToByteArray(
+            "0000003470737368" +  // BMFF box header (4 bytes size + 'pssh')
+            "01000000" +          // Full box header (version = 1 flags = 0)
+            "1077efecc0b24d02" +  // SystemID
+            "ace33c1e52e2fb4b" +
+            "00000001" +          // Number of key ids
+            "60061e017e477e87" +  // Key id
+            "7e57d00d1ed00d1e" +
+            "00000000"            // Size of Data, must be zero
+            );
+
+
+    protected enum ModularDrmTestType {
+        V0_SYNC_TEST,
+        V1_ASYNC_TEST,
+        V2_SYNC_CONFIG_TEST,
+        V3_ASYNC_DRMPREPARED_TEST,
+        V4_SYNC_OFFLINE_KEY,
+        V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER,
+    }
+
+    // TODO: After living on these tests for a while, we can consider grouping them based on
+    // the asset such that each asset is downloaded once and played back with multiple tests.
+    protected void playModularDrmVideoDownload(Uri uri, Uri path, int width, int height,
+            ModularDrmTestType testType) throws Exception {
+        final long DOWNLOAD_TIMEOUT_SECONDS = 600;
+        Log.i(TAG, "Downloading file:" + path);
+        MediaDownloadManager mediaDownloadManager = new MediaDownloadManager(mContext);
+        final long id = mediaDownloadManager.downloadFileWithRetries(
+                uri, path, DOWNLOAD_TIMEOUT_SECONDS, STREAM_RETRIES);
+        assertFalse("Download " + uri + " failed.", id == -1);
+        Uri file = mediaDownloadManager.getUriForDownloadedFile(id);
+        Log.i(TAG, "Downloaded file:" + path + " id:" + id + " uri:" + file);
+
+        try {
+            playModularDrmVideo(file, width, height, testType);
+        } finally {
+            mediaDownloadManager.removeFile(id);
+        }
+    }
+
+    protected void playModularDrmVideo(Uri uri, int width, int height,
+            ModularDrmTestType testType) throws Exception {
+        // Force gc for a clean start
+        System.gc();
+
+        playModularDrmVideoWithRetries(uri, width, height, PLAY_TIME_MS, testType);
+    }
+
+    protected void playModularDrmVideoWithRetries(Uri file, Integer width, Integer height,
+            int playTime, ModularDrmTestType testType) throws Exception {
+
+        // first the synchronous variation
+        boolean playedSuccessfully = false;
+        for (int i = 0; i < STREAM_RETRIES; i++) {
+            try {
+                Log.v(TAG, "playVideoWithRetries(" + testType + ") try " + i);
+                playLoadedModularDrmVideo(file, width, height, playTime, testType);
+
+                playedSuccessfully = true;
+                break;
+            } catch (PrepareFailedException e) {
+                // we can fail because of network issues, so try again
+                Log.w(TAG, "playVideoWithRetries(" + testType + ") failed on try " + i +
+                        ", trying playback again");
+                mMediaPlayer.stop();
+                mMediaPlayer.reset();
+            }
+        }
+        assertTrue("Stream did not play successfully after all attempts (syncDrmSetup)",
+                playedSuccessfully);
+    }
+
+    /**
+     * Play a video which has already been loaded with setDataSource().
+     * The DRM setup is performed synchronously.
+     *
+     * @param file data source
+     * @param width width of the video to verify, or null to skip verification
+     * @param height height of the video to verify, or null to skip verification
+     * @param playTime length of time to play video, or 0 to play entire video
+     * @param testType test type
+     */
+    private void playLoadedModularDrmVideo(final Uri file, final Integer width,
+            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
+
+        switch (testType) {
+            case V0_SYNC_TEST:
+            case V1_ASYNC_TEST:
+            case V2_SYNC_CONFIG_TEST:
+            case V3_ASYNC_DRMPREPARED_TEST:
+            case V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER:
+                playLoadedModularDrmVideo_Generic(file, width, height, playTime, testType);
+                break;
+
+            case V4_SYNC_OFFLINE_KEY:
+                playLoadedModularDrmVideo_V4_offlineKey(file, width, height, playTime);
+                break;
+        }
+    }
+
+    private void playLoadedModularDrmVideo_Generic(final Uri file, final Integer width,
+            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
+
+        final float leftVolume = 0.5f;
+        final float rightVolume = 0.5f;
+
+        mAudioOnly = (width == 0);
+
+        try {
+            Log.v(TAG, "playLoadedVideo: setDataSource()");
+            mMediaPlayer.setDataSource(mContext, file);
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new PrepareFailedException();
+        }
+
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
+            @Override
+            public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
+                Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
+                mOnVideoSizeChangedCalled.signal();
+            }
+        });
+        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                fail("Media player had error " + what + " playing video");
+                return true;
+            }
+        });
+
+        try {
+            switch (testType) {
+            case V0_SYNC_TEST:
+                preparePlayerAndDrm_V0_syncDrmSetup();
+                break;
+
+            case V1_ASYNC_TEST:
+                preparePlayerAndDrm_V1_asyncDrmSetup();
+                break;
+
+            case V2_SYNC_CONFIG_TEST:
+                preparePlayerAndDrm_V2_syncDrmSetupPlusConfig();
+                break;
+
+            case V3_ASYNC_DRMPREPARED_TEST:
+                preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener();
+                break;
+
+            case V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER:
+                preparePlayerAndDrm_V5_asyncDrmSetupWithHandler();
+                break;
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new PrepareFailedException();
+        }
+
+
+        final Monitor playbackCompleted = new Monitor();
+        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override
+            public void onCompletion(MediaPlayer mp) {
+                Log.v(TAG, "playLoadedVideo: onCompletion");
+                playbackCompleted.signal();
+            }
+        });
+
+        Log.v(TAG, "playLoadedVideo: start()");
+        mMediaPlayer.start();
+        if (!mAudioOnly) {
+            mOnVideoSizeChangedCalled.waitForSignal();
+        }
+        mMediaPlayer.setVolume(leftVolume, rightVolume);
+
+        // waiting to complete
+        if (playTime == 0) {
+            Log.v(TAG, "playLoadedVideo: waiting for playback completion");
+            playbackCompleted.waitForSignal();
+        } else {
+            Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
+            playbackCompleted.waitForSignal(playTime);
+        }
+
+        Log.v(TAG, "playLoadedVideo: stopping");
+        mMediaPlayer.stop();
+        Log.v(TAG, "playLoadedVideo: stopped");
+
+        try {
+            Log.v(TAG, "playLoadedVideo: releaseDrm");
+            mMediaPlayer.releaseDrm();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PrepareFailedException();
+        }
+    }
+
+    private void preparePlayerAndDrm_V0_syncDrmSetup() throws Exception {
+        Log.v(TAG, "preparePlayerAndDrm_V0: calling prepare()");
+        mMediaPlayer.prepare();
+
+        DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
+        if (drmInfo != null) {
+            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
+                    MediaDrm.KEY_TYPE_STREAMING);
+            Log.v(TAG, "preparePlayerAndDrm_V0: setupDrm done!");
+        }
+    }
+
+    private void preparePlayerAndDrm_V1_asyncDrmSetup() throws InterruptedException {
+        Monitor onPreparedCalled = new Monitor();
+        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
+
+        mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
+            @Override
+            public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
+                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo" + drmInfo);
+
+                // in the callback (async mode) so handling exceptions here
+                try {
+                    setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
+                            MediaDrm.KEY_TYPE_STREAMING);
+                } catch (Exception e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V1: setupDrm EXCEPTION " + e);
+                    asyncSetupDrmError.set(true);
+                }
+
+                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo done!");
+            }
+        });
+
+        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                Log.v(TAG, "preparePlayerAndDrm_V1: onPrepared");
+
+                onPreparedCalled.signal();
+            }
+        });
+
+        Log.v(TAG, "preparePlayerAndDrm_V1: calling prepareAsync()");
+        mMediaPlayer.prepareAsync();
+
+        // Waiting till the player is prepared
+        onPreparedCalled.waitForSignal();
+
+        // to handle setupDrm error (async) in the main thread rather than the callback
+        if (asyncSetupDrmError.get()) {
+            fail("preparePlayerAndDrm_V1: setupDrm");
+        }
+    }
+
+    private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig() throws Exception {
+        mMediaPlayer.setOnDrmConfigHelper(new MediaPlayer.OnDrmConfigHelper() {
+            @Override
+            public void onDrmConfig(MediaPlayer mp) {
+                String WIDEVINE_SECURITY_LEVEL_3 = "L3";
+                String SECURITY_LEVEL_PROPERTY = "securityLevel";
+
+                try {
+                    String level = mp.getDrmPropertyString(SECURITY_LEVEL_PROPERTY);
+                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: " +
+                            SECURITY_LEVEL_PROPERTY + " -> " + level);
+                    mp.setDrmPropertyString(SECURITY_LEVEL_PROPERTY, WIDEVINE_SECURITY_LEVEL_3);
+                    level = mp.getDrmPropertyString(SECURITY_LEVEL_PROPERTY);
+                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: " +
+                            SECURITY_LEVEL_PROPERTY + " -> " + level);
+                } catch (MediaPlayer.NoDrmSchemeException e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V2: NoDrmSchemeException");
+                } catch (Exception e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V2: onDrmConfig EXCEPTION " + e);
+                }
+            }
+        });
+
+        Log.v(TAG, "preparePlayerAndDrm_V2: calling prepare()");
+        mMediaPlayer.prepare();
+
+        DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
+        if (drmInfo != null) {
+            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
+                    MediaDrm.KEY_TYPE_STREAMING);
+            Log.v(TAG, "preparePlayerAndDrm_V2: setupDrm done!");
+        }
+    }
+
+    private void preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener()
+            throws InterruptedException {
+        Monitor onPreparedCalled = new Monitor();
+        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
+
+        mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
+            @Override
+            public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo" + drmInfo);
+
+                // DRM preperation
+                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
+                if (supportedSchemes.length == 0) {
+                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: No supportedSchemes");
+                    asyncSetupDrmError.set(true);
+                    return;
+                }
+
+                // setting up with the first supported UUID
+                // instead of supportedSchemes[0] in GTS
+                UUID drmScheme = CLEARKEY_SCHEME_UUID;
+                Log.d(TAG, "preparePlayerAndDrm_V3: onDrmInfo: selected " + drmScheme);
+
+                try {
+                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: calling prepareDrm");
+                    mp.prepareDrm(drmScheme);
+                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: called prepareDrm");
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: prepareDrm exception " + e);
+                    asyncSetupDrmError.set(true);
+                    return;
+                }
+
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo done!");
+            }
+        });
+
+        mMediaPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() {
+            @Override
+            public void onDrmPrepared(MediaPlayer mp, int status) {
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared status: " + status);
+
+                assertTrue("preparePlayerAndDrm_V3: onDrmPrepared did not succeed",
+                           status == MediaPlayer.PREPARE_DRM_STATUS_SUCCESS);
+
+                DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
+
+                // in the callback (async mode) so handling exceptions here
+                try {
+                    setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
+                            MediaDrm.KEY_TYPE_STREAMING);
+                } catch (Exception e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V3: setupDrm EXCEPTION " + e);
+                    asyncSetupDrmError.set(true);
+                }
+
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared done!");
+            }
+        });
+
+        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                Log.v(TAG, "preparePlayerAndDrm_V3: onPrepared");
+
+                onPreparedCalled.signal();
+                Log.v(TAG, "preparePlayerAndDrm_V3: onPrepared done!");
+            }
+        });
+
+        Log.v(TAG, "preparePlayerAndDrm_V3: calling prepareAsync()");
+        mMediaPlayer.prepareAsync();
+
+        // Waiting till the player is prepared
+        onPreparedCalled.waitForSignal();
+
+        // to handle setupDrm error (async) in the main thread rather than the callback
+        if (asyncSetupDrmError.get()) {
+            fail("preparePlayerAndDrm_V3: setupDrm");
+        }
+    }
+
+    private void playLoadedModularDrmVideo_V4_offlineKey(final Uri file, final Integer width,
+            final Integer height, int playTime) throws Exception {
+        final float leftVolume = 0.5f;
+        final float rightVolume = 0.5f;
+
+        mAudioOnly = (width == 0);
+
+        Log.v(TAG, "playLoadedModularDrmVideo_V4_offlineKey: setDisplay " +
+                mActivity.getSurfaceHolder());
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
+            @Override
+            public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
+                Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
+                mOnVideoSizeChangedCalled.signal();
+            }
+        });
+        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                fail("Media player had error " + what + " playing video");
+                return true;
+            }
+        });
+
+        final Monitor playbackCompleted = new Monitor();
+        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override
+            public void onCompletion(MediaPlayer mp) {
+                Log.v(TAG, "playLoadedVideo: onCompletion");
+                playbackCompleted.signal();
+            }
+        });
+
+        DrmInfo drmInfo = null;
+
+        for (int round = 0; round < 2 ; round++) {
+            boolean keyRequestRound = (round == 0);
+            boolean restoreRound = (round == 1);
+            Log.v(TAG, "playLoadedVideo: round " + round);
+
+            try {
+                Log.v(TAG, "playLoadedVideo: setDataSource()");
+                mMediaPlayer.setDataSource(mContext, file);
+
+                Log.v(TAG, "playLoadedVideo: prepare()");
+                mMediaPlayer.prepare();
+
+                // but preparing the DRM every time with proper key request type
+                drmInfo = mMediaPlayer.getDrmInfo();
+                if (drmInfo != null) {
+                    if (keyRequestRound) {
+                        // asking for offline keys
+                        setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
+                                 MediaDrm.KEY_TYPE_OFFLINE);
+                    } else if (restoreRound) {
+                        setupDrmRestore(drmInfo, true /* prepareDrm */);
+                    } else {
+                        fail("preparePlayer: unexpected round " + round);
+                    }
+                    Log.v(TAG, "preparePlayer: setupDrm done!");
+                }
+
+            } catch (IOException e) {
+                e.printStackTrace();
+                throw new PrepareFailedException();
+            }
+
+            Log.v(TAG, "playLoadedVideo: start()");
+            mMediaPlayer.start();
+            if (!mAudioOnly) {
+                mOnVideoSizeChangedCalled.waitForSignal();
+            }
+            mMediaPlayer.setVolume(leftVolume, rightVolume);
+
+            // waiting to complete
+            if (playTime == 0) {
+                Log.v(TAG, "playLoadedVideo: waiting for playback completion");
+                playbackCompleted.waitForSignal();
+            } else {
+                Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
+                playbackCompleted.waitForSignal(playTime);
+            }
+
+            Log.v(TAG, "playLoadedVideo: stopping");
+            mMediaPlayer.stop();
+            Log.v(TAG, "playLoadedVideo: stopped");
+
+            try {
+                if (drmInfo != null) {
+                    if (restoreRound) {
+                        // releasing the offline key
+                        setupDrm(null /* drmInfo */, false /* prepareDrm */,
+                                 true /* synchronousNetworking */, MediaDrm.KEY_TYPE_RELEASE);
+                        Log.v(TAG, "playLoadedVideo: released offline keys");
+                    }
+
+                    Log.v(TAG, "playLoadedVideo: releaseDrm");
+                    mMediaPlayer.releaseDrm();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+                throw new PrepareFailedException();
+            }
+
+            if (keyRequestRound) {
+                playbackCompleted.reset();
+                final int SLEEP_BETWEEN_ROUNDS = 1000;
+                Thread.sleep(SLEEP_BETWEEN_ROUNDS);
+
+                Log.v(TAG, "playLoadedVideo: reset");
+                mMediaPlayer.reset();
+            }
+        } // for
+    }
+
+    private void preparePlayerAndDrm_V5_asyncDrmSetupWithHandler()
+            throws InterruptedException {
+        Monitor onPreparedCalled = new Monitor();
+        Monitor onDrmPreparedCalled = new Monitor();
+        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
+
+        Log.v(TAG, "preparePlayerAndDrm_V5: started " +  Thread.currentThread());
+        final HandlerThread handlerThread = new HandlerThread("ModDrmHandlerThread");
+        handlerThread.start();
+        Handler handler = new Handler(handlerThread.getLooper());
+
+        mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
+            @Override
+            public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
+                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo " + drmInfo +
+                        " " + Thread.currentThread());
+
+                // DRM preperation
+                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
+                if (supportedSchemes.length == 0) {
+                    Log.e(TAG, "preparePlayerAndDrm_V5: onDrmInfo: No supportedSchemes");
+                    asyncSetupDrmError.set(true);
+                    // we won't call prepareDrm anymore but need to get passed the wait
+                    onDrmPreparedCalled.signal();
+                    return;
+                }
+
+                // instead of supportedSchemes[0] in GTS
+                UUID drmScheme = CLEARKEY_SCHEME_UUID;
+                Log.d(TAG, "preparePlayerAndDrm_V5: onDrmInfo: selected " + drmScheme);
+
+                try {
+                    Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo: calling prepareDrm");
+                    mp.prepareDrm(drmScheme);
+                    Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo: called prepareDrm");
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    Log.e(TAG, "preparePlayerAndDrm_V5: onDrmInfo: prepareDrm exception " + e);
+                    asyncSetupDrmError.set(true);
+                    // need to get passed the wait
+                    onDrmPreparedCalled.signal();
+                    return;
+                }
+
+                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo done!");
+            }
+        }, handler);
+
+        mMediaPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() {
+            @Override
+            public void onDrmPrepared(MediaPlayer mp, int status) {
+                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmPrepared status: " + status +
+                        " " + Thread.currentThread());
+
+                assertTrue("preparePlayerAndDrm_V5: onDrmPrepared did not succeed",
+                        status == MediaPlayer.PREPARE_DRM_STATUS_SUCCESS);
+
+                DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
+
+                // in the callback (async mode) so handling exceptions here
+                try {
+                    setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
+                            MediaDrm.KEY_TYPE_STREAMING);
+                } catch (Exception e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V5: setupDrm EXCEPTION " + e);
+                    asyncSetupDrmError.set(true);
+                }
+
+                onDrmPreparedCalled.signal();
+                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmPrepared done!");
+            }
+        }, handler);
+
+        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                Log.v(TAG, "preparePlayerAndDrm_V5: onPrepared " + Thread.currentThread());
+
+                onPreparedCalled.signal();
+                Log.v(TAG, "preparePlayerAndDrm_V5: onPrepared done!");
+            }
+        });
+
+        Log.v(TAG, "preparePlayerAndDrm_V5: calling prepareAsync()");
+        mMediaPlayer.prepareAsync();
+
+        // Waiting till the player is prepared
+        onPreparedCalled.waitForSignal();
+        // Unlike v3, onDrmPrepared is not synced to onPrepared b/c of its own thread handler
+        onDrmPreparedCalled.waitForSignal();
+
+        // to handle setupDrm error (async) in the main thread rather than the callback
+        if (asyncSetupDrmError.get()) {
+            fail("preparePlayerAndDrm_V5: setupDrm");
+        }
+
+        // stop the handler thread; callbacks are processed by now.
+        handlerThread.quit();
+    }
+
+    /*
+     * Sets up the DRM for the first DRM scheme from the supported list.
+     *
+     * @param drmInfo DRM info of the source
+     * @param prepareDrm whether prepareDrm should be called
+     * @param synchronousNetworking whether the network operation of key request/response will
+     *        be performed synchronously
+     */
+    private void setupDrm(DrmInfo drmInfo, boolean prepareDrm, boolean synchronousNetworking,
+            int keyType) throws Exception {
+        Log.d(TAG, "setupDrm: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm +
+                " synchronousNetworking: " + synchronousNetworking);
+        try {
+            byte[] initData = null;
+            String mime = null;
+            String keyTypeStr = "Unexpected";
+
+            switch (keyType) {
+            case MediaDrm.KEY_TYPE_STREAMING:
+            case MediaDrm.KEY_TYPE_OFFLINE:
+                // DRM preparation
+                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
+                if (supportedSchemes.length == 0) {
+                    fail("setupDrm: No supportedSchemes");
+                }
+
+                // instead of supportedSchemes[0] in GTS
+                UUID drmScheme = CLEARKEY_SCHEME_UUID;
+                Log.d(TAG, "setupDrm: selected " + drmScheme);
+
+                if (prepareDrm) {
+                    mMediaPlayer.prepareDrm(drmScheme);
+                }
+
+                initData = drmInfo.getPssh().get(drmScheme);
+                // diverging from GTS
+                if (initData == null) {
+                    initData = CLEARKEY_PSSH;
+                    Log.d(TAG, "setupDrm: CLEARKEY scheme not found in PSSH. Using default data.");
+                }
+                Log.d(TAG, "setupDrm: initData[" + drmScheme + "]: " + initData);
+
+                // diverging from GTS
+                mime = "cenc";
+
+                keyTypeStr = (keyType == MediaDrm.KEY_TYPE_STREAMING) ?
+                        "KEY_TYPE_STREAMING" : "KEY_TYPE_OFFLINE";
+                break;
+
+            case MediaDrm.KEY_TYPE_RELEASE:
+                if (mKeySetId == null) {
+                    fail("setupDrm: KEY_TYPE_RELEASE requires a valid keySetId.");
+                }
+                keyTypeStr = "KEY_TYPE_RELEASE";
+                break;
+
+            default:
+                fail("setupDrm: Unexpected keyType " + keyType);
+            }
+
+            final MediaDrm.KeyRequest request = mMediaPlayer.getKeyRequest(
+                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
+                    initData,
+                    mime,
+                    keyType,
+                    null /* optionalKeyRequestParameters */
+                    );
+
+            Log.d(TAG, "setupDrm: mMediaPlayer.getKeyRequest(" + keyTypeStr +
+                    ") request -> " + request);
+
+            // diverging from GTS
+            byte[][] clearKeys = new byte[][] { CLEAR_KEY_CENC };
+            byte[] response = createKeysResponse(request, clearKeys);
+
+            // null is returned when the response is for a streaming or release request.
+            byte[] keySetId = mMediaPlayer.provideKeyResponse(
+                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
+                    response);
+            Log.d(TAG, "setupDrm: provideKeyResponse -> " + keySetId);
+            // storing offline key for a later restore
+            mKeySetId = (keyType == MediaDrm.KEY_TYPE_OFFLINE) ? keySetId : null;
+
+        } catch (MediaPlayer.NoDrmSchemeException e) {
+            Log.d(TAG, "setupDrm: NoDrmSchemeException");
+            e.printStackTrace();
+            throw e;
+        } catch (MediaPlayer.ProvisioningNetworkErrorException e) {
+            Log.d(TAG, "setupDrm: ProvisioningNetworkErrorException");
+            e.printStackTrace();
+            throw e;
+        } catch (MediaPlayer.ProvisioningServerErrorException e) {
+            Log.d(TAG, "setupDrm: ProvisioningServerErrorException");
+            e.printStackTrace();
+            throw e;
+        } catch (UnsupportedSchemeException e) {
+            Log.d(TAG, "setupDrm: UnsupportedSchemeException");
+            e.printStackTrace();
+            throw e;
+        } catch (ResourceBusyException e) {
+            Log.d(TAG, "setupDrm: ResourceBusyException");
+            e.printStackTrace();
+            throw e;
+        } catch (Exception e) {
+            Log.d(TAG, "setupDrm: Exception " + e);
+            e.printStackTrace();
+            throw e;
+        }
+    } // setupDrm
+
+    private void setupDrmRestore(DrmInfo drmInfo, boolean prepareDrm) throws Exception {
+        Log.d(TAG, "setupDrmRestore: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm);
+        try {
+            if (prepareDrm) {
+                // DRM preparation
+                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
+                if (supportedSchemes.length == 0) {
+                    fail("setupDrmRestore: No supportedSchemes");
+                }
+
+                // instead of supportedSchemes[0] in GTS
+                UUID drmScheme = CLEARKEY_SCHEME_UUID;
+                Log.d(TAG, "setupDrmRestore: selected " + drmScheme);
+
+                mMediaPlayer.prepareDrm(drmScheme);
+            }
+
+            if (mKeySetId == null) {
+                fail("setupDrmRestore: Offline key has not been setup.");
+            }
+
+            mMediaPlayer.restoreKeys(mKeySetId);
+
+        } catch (MediaPlayer.NoDrmSchemeException e) {
+            Log.v(TAG, "setupDrmRestore: NoDrmSchemeException");
+            e.printStackTrace();
+            throw e;
+        } catch (Exception e) {
+            Log.v(TAG, "setupDrmRestore: Exception " + e);
+            e.printStackTrace();
+            throw e;
+        }
+    } // setupDrmRestore
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    // Diverging from GTS
+
+    // Clearkey helpers
+
+    /**
+     * Convert a hex string into byte array.
+     */
+    private static byte[] hexStringToByteArray(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) +
+                                   Character.digit(s.charAt(i + 1), 16));
+        }
+        return data;
+    }
+
+    /**
+     * Extracts key ids from the pssh blob returned by getKeyRequest() and
+     * places it in keyIds.
+     * keyRequestBlob format (section 5.1.3.1):
+     * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+     *
+     * @return size of keyIds vector that contains the key ids, 0 for error
+     */
+    private int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
+        if (0 == keyRequestBlob.length || keyIds == null) {
+            Log.e(TAG, "getKeyIds: Empty keyRequestBlob or null keyIds.");
+            return 0;
+        }
+
+        String jsonLicenseRequest = new String(keyRequestBlob);
+        keyIds.clear();
+
+        try {
+            JSONObject license = new JSONObject(jsonLicenseRequest);
+            Log.v(TAG, "getKeyIds: license: " + license);
+            final JSONArray ids = license.getJSONArray("kids");
+            Log.v(TAG, "getKeyIds: ids: " + ids);
+            for (int i = 0; i < ids.length(); ++i) {
+                keyIds.add(ids.getString(i));
+            }
+        } catch (JSONException e) {
+            Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
+            return 0;
+        }
+        return keyIds.size();
+    }
+
+    /**
+     * Creates the JSON Web Key string.
+     *
+     * @return JSON Web Key string.
+     */
+    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
+        String jwkSet = "{\"keys\":[";
+        for (int i = 0; i < keyIds.size(); ++i) {
+            String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
+            String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
+
+            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
+                    "\",\"k\":\"" + key + "\"}";
+        }
+        jwkSet += "]}";
+        return jwkSet;
+    }
+
+    /**
+     * Retrieves clear key ids from KeyRequest and creates the response in place.
+     */
+    private byte[] createKeysResponse(MediaDrm.KeyRequest keyRequest, byte[][] clearKeys) {
+
+        Vector<String> keyIds = new Vector<String>();
+        if (0 == getKeyIds(keyRequest.getData(), keyIds)) {
+            Log.e(TAG, "No key ids found in initData");
+            return null;
+        }
+
+        if (clearKeys.length != keyIds.size()) {
+            Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
+                    keyIds.size() + ", keys=" + clearKeys.length);
+            return null;
+        }
+
+        // Base64 encodes clearkeys. Keys are known to the application.
+        Vector<String> keys = new Vector<String>();
+        for (int i = 0; i < clearKeys.length; ++i) {
+            String clearKey = Base64.encodeToString(clearKeys[i],
+                    Base64.NO_PADDING | Base64.NO_WRAP);
+            keys.add(clearKey);
+        }
+
+        String jwkSet = createJsonWebKeySet(keyIds, keys);
+        byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
+
+        return jsonResponse;
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    // Playback/download helpers
+
+    private static class MediaDownloadManager {
+        private static final String TAG = "MediaDownloadManager";
+
+        private final Context mContext;
+        private final DownloadManager mDownloadManager;
+
+        public MediaDownloadManager(Context context) {
+            mContext = context;
+            mDownloadManager =
+                    (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
+        }
+
+        public long downloadFileWithRetries(Uri uri, Uri file, long timeout, int retries)
+            throws Exception {
+            long id = -1;
+            for (int i = 0; i < retries; i++) {
+                try {
+                    id = downloadFile(uri, file, timeout);
+                    if (id != -1) {
+                        break;
+                    }
+                } catch (Exception e) {
+                    removeFile(id);
+                    Log.w(TAG, "Download failed " + i + " times ");
+                }
+            }
+            return id;
+        }
+
+        public long downloadFile(Uri uri, Uri file, long timeout) throws Exception {
+            Log.i(TAG, "uri:" + uri + " file:" + file + " wait:" + timeout + " Secs");
+            final DownloadReceiver receiver = new DownloadReceiver();
+            long id = -1;
+            try {
+                IntentFilter intentFilter =
+                        new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
+                mContext.registerReceiver(receiver, intentFilter);
+
+                Request request = new Request(uri);
+                request.setDestinationUri(file);
+                id = mDownloadManager.enqueue(request);
+                Log.i(TAG, "enqueue:" + id);
+
+                receiver.waitForDownloadComplete(timeout, id);
+            } finally {
+                mContext.unregisterReceiver(receiver);
+            }
+            return id;
+        }
+
+        public void removeFile(long id) {
+            Log.i(TAG, "removeFile:" + id);
+            mDownloadManager.remove(id);
+        }
+
+        public Uri getUriForDownloadedFile(long id) {
+            return mDownloadManager.getUriForDownloadedFile(id);
+        }
+
+        private final class DownloadReceiver extends BroadcastReceiver {
+            private HashSet<Long> mCompleteIds = new HashSet<>();
+
+            public DownloadReceiver() {
+            }
+
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                synchronized (mCompleteIds) {
+                    if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
+                        mCompleteIds.add(
+                                intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1));
+                        mCompleteIds.notifyAll();
+                    }
+                }
+            }
+
+            private boolean isCompleteLocked(long... ids) {
+                for (long id : ids) {
+                    if (!mCompleteIds.contains(id)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            public void waitForDownloadComplete(long timeoutSecs, long... waitForIds)
+                throws InterruptedException {
+                if (waitForIds.length == 0) {
+                    throw new IllegalArgumentException("Missing IDs to wait for");
+                }
+
+                final long startTime = SystemClock.elapsedRealtime();
+                do {
+                    synchronized (mCompleteIds) {
+                        mCompleteIds.wait(1000);
+                        if (isCompleteLocked(waitForIds)) {
+                            return;
+                        }
+                    }
+                } while ((SystemClock.elapsedRealtime() - startTime) < timeoutSecs * 1000);
+
+                throw new InterruptedException("Timeout waiting for IDs " +
+                        Arrays.toString(waitForIds) + "; received " + mCompleteIds.toString()
+                        + ".  Make sure you have WiFi or some other connectivity for this test.");
+            }
+        }
+
+    }  // MediaDownloadManager
+
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 1102789..e659e19 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -59,7 +59,8 @@
 import java.util.UUID;
 import java.util.Vector;
 import java.util.concurrent.CountDownLatch;
-
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import junit.framework.AssertionFailedError;
 
 /**
@@ -302,6 +303,42 @@
         }
     }
 
+    public void testConcurentPlayAudio() throws Exception {
+        final int resid = R.raw.test1m1s; // MP3 longer than 1m are usualy offloaded
+        final int tolerance = 70;
+
+        List<MediaPlayer> mps = Stream.generate(() -> MediaPlayer.create(mContext, resid))
+                                      .limit(5).collect(Collectors.toList());
+
+        try {
+            for (MediaPlayer mp : mps) {
+                mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+                mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+                assertFalse(mp.isPlaying());
+                mp.start();
+                assertTrue(mp.isPlaying());
+
+                assertFalse(mp.isLooping());
+                mp.setLooping(true);
+                assertTrue(mp.isLooping());
+
+                int pos = mp.getCurrentPosition();
+                assertTrue(pos >= 0);
+
+                Thread.sleep(SLEEP_TIME); // Delay each track to be able to ear them
+            }
+            // Check that all mp3 are playing concurrently here
+            for (MediaPlayer mp : mps) {
+                int pos = mp.getCurrentPosition();
+                Thread.sleep(SLEEP_TIME);
+                assertEquals(pos + SLEEP_TIME, mp.getCurrentPosition(), tolerance);
+            }
+        } finally {
+            mps.forEach(MediaPlayer::release);
+        }
+    }
+
     public void testPlayAudioLooping() throws Exception {
         final int resid = R.raw.testmp3;
 
diff --git a/tests/tests/nativemedia/aaudio/Android.mk b/tests/tests/nativemedia/aaudio/Android.mk
index cc0a1cf..f1fc7fd 100644
--- a/tests/tests/nativemedia/aaudio/Android.mk
+++ b/tests/tests/nativemedia/aaudio/Android.mk
@@ -27,6 +27,7 @@
     src/test_aaudio.cpp \
     src/test_aaudio_misc.cpp \
     src/test_aaudio_callback.cpp \
+    src/test_aaudio_stream_builder.cpp \
     src/utils.cpp \
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
index 3d473c2..ab2cc55 100644
--- a/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
+++ b/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
@@ -24,105 +24,8 @@
 #include "test_aaudio.h"
 #include "utils.h"
 
-// Test AAudioStreamBuilder
-TEST(test_aaudio, aaudio_stream_builder) {
-
-    AAudioStreamBuilder* aaudioBuilder1 = nullptr;
-    AAudioStreamBuilder* aaudioBuilder2 = nullptr;
-
-    // Use an AAudioStreamBuilder to define the stream.
-    aaudio_result_t result = AAudio_createStreamBuilder(&aaudioBuilder1);
-    ASSERT_EQ(AAUDIO_OK, result);
-    ASSERT_NE(nullptr, aaudioBuilder1);
-
-    // Create a second builder and make sure they do not collide.
-    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder2));
-    ASSERT_NE(nullptr, aaudioBuilder2);
-
-    ASSERT_NE(aaudioBuilder1, aaudioBuilder2);
-
-    // Delete the first builder.
-    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder1));
-
-    // Delete the second builder.
-    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder2));
-
-}
-
-
-// Test creating a default stream with specific devices
-void runtest_aaudio_devices(int32_t deviceId, bool expectFail) {
-    AAudioStreamBuilder *aaudioBuilder = nullptr;
-    AAudioStream *aaudioStream = nullptr;
-
-    // Use an AAudioStreamBuilder to define the stream.
-    aaudio_result_t result = AAudio_createStreamBuilder(&aaudioBuilder);
-    ASSERT_EQ(AAUDIO_OK, result);
-    ASSERT_NE(nullptr, aaudioBuilder);
-
-    AAudioStreamBuilder_setDeviceId(aaudioBuilder,deviceId);
-
-    // Create an AAudioStream using the Builder.
-    result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
-    if (expectFail) {
-        ASSERT_NE(AAUDIO_OK, result);
-        ASSERT_EQ(nullptr, aaudioStream);
-    } else {
-        // Pass or fail is OK. Just don't crash.
-        ASSERT_TRUE(((result < 0) && (aaudioStream == nullptr))
-                    || ((result == AAUDIO_OK) && (aaudioStream != nullptr)));
-    }
-
-    // Cleanup
-    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
-    if (aaudioStream != nullptr) {
-        AAudioStream_close(aaudioStream);
-    }
-}
-
-TEST(test_aaudio, aaudio_stream_device_unspecified) {
-    runtest_aaudio_devices(AAUDIO_DEVICE_UNSPECIFIED, false);
-}
-
-/* FIXME - why can we open this device? What is an illegal deviceId?
-TEST(test_aaudio, aaudio_stream_device_absurd) {
-    runtest_aaudio_devices(19736459, true);
-}
-*/
-/* FIXME review
-TEST(test_aaudio, aaudio_stream_device_reasonable) {
-    runtest_aaudio_devices(1, false);
-}
-*/
-
-/* FIXME - why can we open this device? What is an illegal deviceId?
-TEST(test_aaudio, aaudio_stream_device_negative) {
-    runtest_aaudio_devices(-765, true);
-}
-*/
-
-// Test creating a default stream with everything unspecified.
-TEST(test_aaudio, aaudio_stream_unspecified) {
-    AAudioStreamBuilder *aaudioBuilder = nullptr;
-    AAudioStream *aaudioStream = nullptr;
-    aaudio_result_t result = AAUDIO_OK;
-
-    // Use an AAudioStreamBuilder to define the stream.
-    result = AAudio_createStreamBuilder(&aaudioBuilder);
-    ASSERT_EQ(AAUDIO_OK, result);
-    ASSERT_NE(nullptr, aaudioBuilder);
-
-    // Create an AAudioStream using the Builder.
-    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
-    ASSERT_NE(nullptr, aaudioStream);
-
-    // Cleanup
-    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
-    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
-}
-
 // Test Writing to an AAudioStream
-void runtest_aaudio_stream(aaudio_sharing_mode_t requestedSharingMode) {
+static void runtest_aaudio_stream(aaudio_sharing_mode_t requestedSharingMode) {
     StreamBuilderHelper helper{requestedSharingMode};
 
     int writeLoops = 0;
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_callback.cpp b/tests/tests/nativemedia/aaudio/src/test_aaudio_callback.cpp
index 525c21e..dac6684 100644
--- a/tests/tests/nativemedia/aaudio/src/test_aaudio_callback.cpp
+++ b/tests/tests/nativemedia/aaudio/src/test_aaudio_callback.cpp
@@ -110,8 +110,8 @@
 }
 
 // Test Writing to an AAudioStream using a Callback
-void runtest_aaudio_callback(aaudio_sharing_mode_t requestedSharingMode,
-                             int32_t framesPerDataCallback) {
+static void runtest_aaudio_callback(aaudio_sharing_mode_t requestedSharingMode,
+                                    int32_t framesPerDataCallback) {
     AAudioCallbackTestData myTestData;
     StreamBuilderHelper helper{requestedSharingMode};
 
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_stream_builder.cpp b/tests/tests/nativemedia/aaudio/src/test_aaudio_stream_builder.cpp
new file mode 100644
index 0000000..d63a935
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/src/test_aaudio_stream_builder.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright 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_NDEBUG 0
+#define LOG_TAG "AAudioTest"
+
+#include <aaudio/AAudio.h>
+#include <android/log.h>
+#include <gtest/gtest.h>
+
+// Creates a builder, the caller takes ownership
+static void create_stream_builder(AAudioStreamBuilder** aaudioBuilder) {
+    aaudio_result_t result = AAudio_createStreamBuilder(aaudioBuilder);
+    ASSERT_EQ(AAUDIO_OK, result);
+    ASSERT_NE(nullptr, *aaudioBuilder);
+}
+
+enum class Expect { FAIL, SUCCEED, NOT_CRASH };
+
+// Tries to open an audio stream using a primed Builder.
+// Takes ownership of the Builder.
+static void try_opening_audio_stream(AAudioStreamBuilder *aaudioBuilder, Expect expect) {
+    // Create an AAudioStream using the Builder.
+    AAudioStream *aaudioStream = nullptr;
+    aaudio_result_t result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
+    if (expect == Expect::FAIL) {
+        ASSERT_NE(AAUDIO_OK, result);
+        ASSERT_EQ(nullptr, aaudioStream);
+    } else if (expect == Expect::SUCCEED) {
+        ASSERT_EQ(AAUDIO_OK, result);
+        ASSERT_NE(nullptr, aaudioStream);
+    } else { // NOT_CRASH
+        ASSERT_TRUE(((result < 0) && (aaudioStream == nullptr))
+                || ((result == AAUDIO_OK) && (aaudioStream != nullptr)));
+    }
+
+    // Cleanup
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
+    if (aaudioStream != nullptr) {
+        ASSERT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
+    }
+}
+
+// Test creating a default stream with specific devices
+static void runtest_aaudio_devices(int32_t deviceId, Expect expect) {
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+    AAudioStreamBuilder_setDeviceId(aaudioBuilder, deviceId);
+    try_opening_audio_stream(aaudioBuilder, expect);
+}
+
+TEST(test_aaudio, aaudio_stream_device_unspecified) {
+    runtest_aaudio_devices(AAUDIO_DEVICE_UNSPECIFIED, Expect::NOT_CRASH);
+}
+
+/* FIXME - why can we open this device? What is an illegal deviceId?
+TEST(test_aaudio, aaudio_stream_device_absurd) {
+    runtest_aaudio_devices(19736459, true);
+}
+*/
+/* FIXME review
+TEST(test_aaudio, aaudio_stream_device_reasonable) {
+    runtest_aaudio_devices(1, false);
+}
+*/
+
+/* FIXME - why can we open this device? What is an illegal deviceId?
+TEST(test_aaudio, aaudio_stream_device_negative) {
+    runtest_aaudio_devices(-765, true);
+}
+*/
+
+// Test creating a default stream with everything unspecified.
+TEST(test_aaudio, aaudio_stream_unspecified) {
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+
+    // Create an AAudioStream using the Builder.
+    AAudioStream *aaudioStream = nullptr;
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+    ASSERT_NE(nullptr, aaudioStream);
+
+    // Cleanup
+    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
+}
+
+class AAudioStreamBuilderSamplingRateTest : public ::testing::TestWithParam<int32_t> {
+  public:
+    static std::string getTestName(const ::testing::TestParamInfo<int32_t>& info) {
+        return info.param >= 0 ? std::to_string(info.param) : "_" + std::to_string(-info.param);
+    }
+  protected:
+    static bool isValidSamplingRate(int32_t sr) {
+        return sr == AAUDIO_UNSPECIFIED || (sr >= 8000 && sr <= 1000000);
+    }
+};
+
+TEST_P(AAudioStreamBuilderSamplingRateTest, openStream) {
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+    AAudioStreamBuilder_setSampleRate(aaudioBuilder, GetParam());
+    try_opening_audio_stream(
+            aaudioBuilder, isValidSamplingRate(GetParam()) ? Expect::SUCCEED : Expect::FAIL);
+}
+
+INSTANTIATE_TEST_CASE_P(SR, AAudioStreamBuilderSamplingRateTest,
+        ::testing::Values(
+                // Commonly used values
+                AAUDIO_UNSPECIFIED, 8000, 11025, 16000, 22050, 44100, 48000, 88200, 96000,
+                // Odd values
+                AAUDIO_UNSPECIFIED - 1, AAUDIO_UNSPECIFIED + 1, 1234, 1000000, 10000000),
+        &AAudioStreamBuilderSamplingRateTest::getTestName);
+
+class AAudioStreamBuilderChannelCountTest : public ::testing::TestWithParam<int32_t> {
+  public:
+    static std::string getTestName(const ::testing::TestParamInfo<int32_t>& info) {
+        return info.param >= 0 ? std::to_string(info.param) : "_" + std::to_string(-info.param);
+    }
+  protected:
+    static bool isValidChannelCount(int32_t cc) {
+        return cc == AAUDIO_UNSPECIFIED || (cc >= 1 && cc <= 8);
+    }
+};
+
+TEST_P(AAudioStreamBuilderChannelCountTest, openStream) {
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+    AAudioStreamBuilder_setChannelCount(aaudioBuilder, GetParam());
+    try_opening_audio_stream(
+            aaudioBuilder, isValidChannelCount(GetParam()) ? Expect::SUCCEED : Expect::FAIL);
+}
+
+INSTANTIATE_TEST_CASE_P(CC, AAudioStreamBuilderChannelCountTest,
+        ::testing::Values(
+                // Reasonable values
+                AAUDIO_UNSPECIFIED, 1, 2, 3, 4, 5, 6, 7, 8,
+                // Odd values
+                AAUDIO_UNSPECIFIED - 1, 9, 100, 1000000, 10000000),
+        &AAudioStreamBuilderChannelCountTest::getTestName);
+
+class AAudioStreamBuilderFormatTest : public ::testing::TestWithParam<aaudio_format_t> {
+  public:
+    static std::string getTestName(const ::testing::TestParamInfo<aaudio_format_t>& info) {
+        return info.param >= 0 ? std::to_string(info.param) : "_" + std::to_string(-info.param);
+    }
+  protected:
+    static bool isValidFormat(aaudio_format_t f) {
+        switch (f) {
+            case AAUDIO_FORMAT_UNSPECIFIED:
+            case AAUDIO_FORMAT_PCM_I16:
+            case AAUDIO_FORMAT_PCM_FLOAT:
+                return true;
+        }
+        return false;
+    }
+};
+
+TEST_P(AAudioStreamBuilderFormatTest, openStream) {
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+    AAudioStreamBuilder_setFormat(aaudioBuilder, GetParam());
+    try_opening_audio_stream(
+            aaudioBuilder, isValidFormat(GetParam()) ? Expect::SUCCEED : Expect::FAIL);
+}
+
+INSTANTIATE_TEST_CASE_P(F, AAudioStreamBuilderFormatTest,
+        ::testing::Values(
+                // Reasonable values
+                AAUDIO_FORMAT_UNSPECIFIED, AAUDIO_FORMAT_PCM_I16, AAUDIO_FORMAT_PCM_FLOAT,
+                // Odd values
+                AAUDIO_FORMAT_INVALID, AAUDIO_FORMAT_INVALID - 1, 100, 1000000, 10000000),
+        &AAudioStreamBuilderFormatTest::getTestName);
+
+class AAudioStreamBuilderSharingModeTest : public ::testing::TestWithParam<aaudio_sharing_mode_t> {
+  public:
+    static std::string getTestName(const ::testing::TestParamInfo<aaudio_sharing_mode_t>& info) {
+        return info.param >= 0 ? std::to_string(info.param) : "_" + std::to_string(-info.param);
+    }
+  protected:
+    static bool isValidSharingMode(aaudio_sharing_mode_t f) {
+        return f == AAUDIO_SHARING_MODE_SHARED || f == AAUDIO_SHARING_MODE_EXCLUSIVE;
+    }
+};
+
+TEST_P(AAudioStreamBuilderSharingModeTest, openStream) {
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+    AAudioStreamBuilder_setFormat(aaudioBuilder, GetParam());
+    try_opening_audio_stream(
+            aaudioBuilder, isValidSharingMode(GetParam()) ? Expect::SUCCEED : Expect::FAIL);
+}
+
+INSTANTIATE_TEST_CASE_P(SM, AAudioStreamBuilderSharingModeTest,
+        ::testing::Values(
+                // Reasonable values
+                AAUDIO_SHARING_MODE_SHARED, AAUDIO_SHARING_MODE_EXCLUSIVE,
+                // Odd values
+                -1, 100, 1000000, 10000000),
+        &AAudioStreamBuilderSharingModeTest::getTestName);
+
+class AAudioStreamBuilderDirectionTest : public ::testing::TestWithParam<aaudio_direction_t> {
+  public:
+    static std::string getTestName(const ::testing::TestParamInfo<aaudio_direction_t>& info) {
+        return info.param >= 0 ? std::to_string(info.param) : "_" + std::to_string(-info.param);
+    }
+  protected:
+    static bool isValidSharingMode(aaudio_direction_t f) {
+        return f == AAUDIO_DIRECTION_OUTPUT || f == AAUDIO_DIRECTION_INPUT;
+    }
+};
+
+TEST_P(AAudioStreamBuilderDirectionTest, openStream) {
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+    AAudioStreamBuilder_setFormat(aaudioBuilder, GetParam());
+    try_opening_audio_stream(
+            aaudioBuilder, isValidSharingMode(GetParam()) ? Expect::SUCCEED : Expect::FAIL);
+}
+
+INSTANTIATE_TEST_CASE_P(SD, AAudioStreamBuilderDirectionTest,
+        ::testing::Values(
+                // Reasonable values
+                AAUDIO_DIRECTION_OUTPUT, AAUDIO_DIRECTION_INPUT,
+                // Odd values
+                -1, 100, 1000000, 10000000),
+        &AAudioStreamBuilderDirectionTest::getTestName);
+
+class AAudioStreamBuilderBufferCapacityTest : public ::testing::TestWithParam<int32_t> {
+  public:
+    static std::string getTestName(const ::testing::TestParamInfo<int32_t>& info) {
+        return info.param >= 0 ? std::to_string(info.param) : "_" + std::to_string(-info.param);
+    }
+  protected:
+    // There is no hard defined limit, the actual maximum capacity depends
+    // on the implementation.
+    static bool isValidCapacity(int32_t bc) {
+        return bc == AAUDIO_UNSPECIFIED || bc >= 0;
+    }
+};
+
+TEST_P(AAudioStreamBuilderBufferCapacityTest, openStream) {
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+    AAudioStreamBuilder_setBufferCapacityInFrames(aaudioBuilder, GetParam());
+    try_opening_audio_stream(
+            aaudioBuilder, isValidCapacity(GetParam()) ? Expect::SUCCEED : Expect::FAIL);
+}
+
+INSTANTIATE_TEST_CASE_P(BC, AAudioStreamBuilderBufferCapacityTest,
+        ::testing::Values(
+                // Reasonable values that should not fail
+                AAUDIO_UNSPECIFIED, 8 * 192, 2 * 1024,
+                // Odd values
+                AAUDIO_UNSPECIFIED - 1),
+        &AAudioStreamBuilderBufferCapacityTest::getTestName);
+
+class AAudioStreamBuilderPerfModeTest : public ::testing::TestWithParam<aaudio_performance_mode_t> {
+  public:
+    static std::string getTestName(const ::testing::TestParamInfo<aaudio_performance_mode_t>& info) {
+        return info.param >= 0 ? std::to_string(info.param) : "_" + std::to_string(-info.param);
+    }
+  protected:
+    static bool isValidPerfMode(aaudio_performance_mode_t pm) {
+        switch (pm) {
+            case AAUDIO_PERFORMANCE_MODE_NONE:
+            case AAUDIO_PERFORMANCE_MODE_POWER_SAVING:
+            case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY:
+                return true;
+        }
+        return false;
+    }
+};
+
+TEST_P(AAudioStreamBuilderPerfModeTest, openStream) {
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, GetParam());
+    try_opening_audio_stream(
+            aaudioBuilder, isValidPerfMode(GetParam()) ? Expect::SUCCEED : Expect::FAIL);
+}
+
+INSTANTIATE_TEST_CASE_P(PM, AAudioStreamBuilderPerfModeTest,
+        ::testing::Values(
+                // Reasonable values
+                AAUDIO_PERFORMANCE_MODE_NONE,
+                AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
+                AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+                // Odd values
+                AAUDIO_UNSPECIFIED - 1, AAUDIO_UNSPECIFIED, 100, 1000000, 10000000),
+        &AAudioStreamBuilderPerfModeTest::getTestName);
diff --git a/tests/tests/net/assets/PerProviderSubscription.xml b/tests/tests/net/assets/PerProviderSubscription.xml
index 7f2d95d..de6c0c6 100644
--- a/tests/tests/net/assets/PerProviderSubscription.xml
+++ b/tests/tests/net/assets/PerProviderSubscription.xml
@@ -8,10 +8,6 @@
       </Type>
     </RTProperties>
     <Node>
-      <NodeName>UpdateIdentifier</NodeName>
-      <Value>12</Value>
-    </Node>
-    <Node>
       <NodeName>i001</NodeName>
       <Node>
         <NodeName>HomeSP</NodeName>
@@ -27,86 +23,14 @@
           <NodeName>RoamingConsortiumOI</NodeName>
           <Value>112233,445566</Value>
         </Node>
-        <Node>
-          <NodeName>IconURL</NodeName>
-          <Value>icon.test.com</Value>
-        </Node>
-        <Node>
-          <NodeName>NetworkID</NodeName>
-          <Node>
-            <NodeName>n001</NodeName>
-            <Node>
-              <NodeName>SSID</NodeName>
-              <Value>TestSSID</Value>
-            </Node>
-            <Node>
-              <NodeName>HESSID</NodeName>
-              <Value>12345678</Value>
-            </Node>
-          </Node>
-          <Node>
-            <NodeName>n002</NodeName>
-            <Node>
-              <NodeName>SSID</NodeName>
-              <Value>NullHESSID</Value>
-            </Node>
-          </Node>
-        </Node>
-        <Node>
-          <NodeName>HomeOIList</NodeName>
-          <Node>
-            <NodeName>h001</NodeName>
-            <Node>
-              <NodeName>HomeOI</NodeName>
-              <Value>11223344</Value>
-            </Node>
-            <Node>
-              <NodeName>HomeOIRequired</NodeName>
-              <Value>true</Value>
-            </Node>
-          </Node>
-          <Node>
-            <NodeName>h002</NodeName>
-            <Node>
-              <NodeName>HomeOI</NodeName>
-              <Value>55667788</Value>
-            </Node>
-            <Node>
-              <NodeName>HomeOIRequired</NodeName>
-              <Value>false</Value>
-            </Node>
-          </Node>
-        </Node>
-        <Node>
-          <NodeName>OtherHomePartners</NodeName>
-          <Node>
-            <NodeName>o001</NodeName>
-            <Node>
-              <NodeName>FQDN</NodeName>
-              <Value>other.fqdn.com</Value>
-            </Node>
-          </Node>
-        </Node>
       </Node>
       <Node>
         <NodeName>Credential</NodeName>
         <Node>
-          <NodeName>CreationDate</NodeName>
-          <Value>2016-01-01T10:00:00Z</Value>
-        </Node>
-        <Node>
-          <NodeName>ExpirationDate</NodeName>
-          <Value>2016-02-01T10:00:00Z</Value>
-        </Node>
-        <Node>
           <NodeName>Realm</NodeName>
           <Value>shaken.stirred.com</Value>
         </Node>
         <Node>
-          <NodeName>CheckAAAServerCertStatus</NodeName>
-          <Value>true</Value>
-        </Node>
-        <Node>
           <NodeName>UsernamePassword</NodeName>
           <Node>
             <NodeName>Username</NodeName>
@@ -117,18 +41,6 @@
             <Value>Ym9uZDAwNw==</Value>
           </Node>
           <Node>
-            <NodeName>MachineManaged</NodeName>
-            <Value>true</Value>
-          </Node>
-          <Node>
-            <NodeName>SoftTokenApp</NodeName>
-            <Value>TestApp</Value>
-          </Node>
-          <Node>
-            <NodeName>AbleToShare</NodeName>
-            <Value>true</Value>
-          </Node>
-          <Node>
             <NodeName>EAPMethod</NodeName>
             <Node>
               <NodeName>EAPType</NodeName>
@@ -163,237 +75,6 @@
           </Node>
         </Node>
       </Node>
-      <Node>
-        <NodeName>Policy</NodeName>
-        <Node>
-          <NodeName>PreferredRoamingPartnerList</NodeName>
-          <Node>
-            <NodeName>p001</NodeName>
-            <Node>
-              <NodeName>FQDN_Match</NodeName>
-              <Value>test1.fqdn.com,exactMatch</Value>
-            </Node>
-            <Node>
-              <NodeName>Priority</NodeName>
-              <Value>127</Value>
-            </Node>
-            <Node>
-              <NodeName>Country</NodeName>
-              <Value>us,fr</Value>
-            </Node>
-          </Node>
-          <Node>
-            <NodeName>p002</NodeName>
-            <Node>
-              <NodeName>FQDN_Match</NodeName>
-              <Value>test2.fqdn.com,includeSubdomains</Value>
-            </Node>
-            <Node>
-              <NodeName>Priority</NodeName>
-              <Value>200</Value>
-            </Node>
-            <Node>
-              <NodeName>Country</NodeName>
-              <Value>*</Value>
-            </Node>
-          </Node>
-        </Node>
-        <Node>
-          <NodeName>MinBackhaulThreshold</NodeName>
-          <Node>
-            <NodeName>m001</NodeName>
-            <Node>
-              <NodeName>NetworkType</NodeName>
-              <Value>home</Value>
-            </Node>
-            <Node>
-              <NodeName>DLBandwidth</NodeName>
-              <Value>23412</Value>
-            </Node>
-            <Node>
-              <NodeName>ULBandwidth</NodeName>
-              <Value>9823</Value>
-            </Node>
-          </Node>
-          <Node>
-            <NodeName>m002</NodeName>
-            <Node>
-              <NodeName>NetworkType</NodeName>
-              <Value>roaming</Value>
-            </Node>
-            <Node>
-              <NodeName>DLBandwidth</NodeName>
-              <Value>9271</Value>
-            </Node>
-            <Node>
-              <NodeName>ULBandwidth</NodeName>
-              <Value>2315</Value>
-            </Node>
-          </Node>
-        </Node>
-        <Node>
-          <NodeName>PolicyUpdate</NodeName>
-          <Node>
-            <NodeName>UpdateInterval</NodeName>
-            <Value>120</Value>
-          </Node>
-          <Node>
-            <NodeName>UpdateMethod</NodeName>
-            <Value>OMA-DM-ClientInitiated</Value>
-          </Node>
-          <Node>
-            <NodeName>Restriction</NodeName>
-            <Value>HomeSP</Value>
-          </Node>
-          <Node>
-            <NodeName>URI</NodeName>
-            <Value>policy.update.com</Value>
-          </Node>
-          <Node>
-            <NodeName>UsernamePassword</NodeName>
-            <Node>
-              <NodeName>Username</NodeName>
-              <Value>updateUser</Value>
-            </Node>
-            <Node>
-              <NodeName>Password</NodeName>
-              <Value>updatePass</Value>
-            </Node>
-          </Node>
-          <Node>
-            <NodeName>TrustRoot</NodeName>
-            <Node>
-              <NodeName>CertURL</NodeName>
-              <Value>update.cert.com</Value>
-            </Node>
-            <Node>
-              <NodeName>CertSHA256Fingerprint</NodeName>
-              <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
-            </Node>
-          </Node>
-        </Node>
-        <Node>
-          <NodeName>SPExclusionList</NodeName>
-          <Node>
-            <NodeName>s001</NodeName>
-            <Node>
-              <NodeName>SSID</NodeName>
-              <Value>excludeSSID</Value>
-            </Node>
-          </Node>
-        </Node>
-        <Node>
-          <NodeName>RequiredProtoPortTuple</NodeName>
-          <Node>
-            <NodeName>r001</NodeName>
-            <Node>
-              <NodeName>IPProtocol</NodeName>
-              <Value>12</Value>
-            </Node>
-            <Node>
-              <NodeName>PortNumber</NodeName>
-              <Value>34,92,234</Value>
-            </Node>
-          </Node>
-        </Node>
-        <Node>
-          <NodeName>MaximumBSSLoadValue</NodeName>
-          <Value>23</Value>
-        </Node>
-      </Node>
-      <Node>
-        <NodeName>CredentialPriority</NodeName>
-        <Value>99</Value>
-      </Node>
-      <Node>
-        <NodeName>AAAServerTrustRoot</NodeName>
-        <Node>
-          <NodeName>a001</NodeName>
-          <Node>
-            <NodeName>CertURL</NodeName>
-            <Value>server1.trust.root.com</Value>
-          </Node>
-          <Node>
-            <NodeName>CertSHA256Fingerprint</NodeName>
-            <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
-          </Node>
-        </Node>
-      </Node>
-      <Node>
-        <NodeName>SubscriptionUpdate</NodeName>
-        <Node>
-          <NodeName>UpdateInterval</NodeName>
-          <Value>120</Value>
-        </Node>
-        <Node>
-          <NodeName>UpdateMethod</NodeName>
-          <Value>SSP-ClientInitiated</Value>
-        </Node>
-        <Node>
-          <NodeName>Restriction</NodeName>
-          <Value>RoamingPartner</Value>
-        </Node>
-        <Node>
-          <NodeName>URI</NodeName>
-          <Value>subscription.update.com</Value>
-        </Node>
-        <Node>
-          <NodeName>UsernamePassword</NodeName>
-          <Node>
-            <NodeName>Username</NodeName>
-            <Value>subscriptionUser</Value>
-          </Node>
-          <Node>
-            <NodeName>Password</NodeName>
-            <Value>subscriptionPass</Value>
-          </Node>
-        </Node>
-        <Node>
-          <NodeName>TrustRoot</NodeName>
-          <Node>
-            <NodeName>CertURL</NodeName>
-            <Value>subscription.update.cert.com</Value>
-          </Node>
-          <Node>
-            <NodeName>CertSHA256Fingerprint</NodeName>
-            <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
-          </Node>
-        </Node>
-      </Node>
-      <Node>
-        <NodeName>SubscriptionParameter</NodeName>
-        <Node>
-          <NodeName>CreationDate</NodeName>
-          <Value>2016-02-01T10:00:00Z</Value>
-        </Node>
-        <Node>
-          <NodeName>ExpirationDate</NodeName>
-          <Value>2016-03-01T10:00:00Z</Value>
-        </Node>
-        <Node>
-          <NodeName>TypeOfSubscription</NodeName>
-          <Value>Gold</Value>
-        </Node>
-        <Node>
-          <NodeName>UsageLimits</NodeName>
-          <Node>
-            <NodeName>DataLimit</NodeName>
-            <Value>921890</Value>
-          </Node>
-          <Node>
-            <NodeName>StartDate</NodeName>
-            <Value>2016-12-01T10:00:00Z</Value>
-          </Node>
-          <Node>
-            <NodeName>TimeLimit</NodeName>
-            <Value>120</Value>
-          </Node>
-          <Node>
-            <NodeName>UsageTimePeriod</NodeName>
-            <Value>99910</Value>
-          </Node>
-        </Node>
-      </Node>
     </Node>
   </Node>
 </MgmtTree>
diff --git a/tests/tests/net/src/android/net/wifi/cts/FakeKeys.java b/tests/tests/net/src/android/net/wifi/cts/FakeKeys.java
index f422c2f..f875301 100644
--- a/tests/tests/net/src/android/net/wifi/cts/FakeKeys.java
+++ b/tests/tests/net/src/android/net/wifi/cts/FakeKeys.java
@@ -73,6 +73,26 @@
             "-----END CERTIFICATE-----\n";
     public static final X509Certificate CA_CERT1 = loadCertificate(CA_CERT1_STRING);
 
+    private static final String CA_PUBLIC_CERT_STRING = "-----BEGIN CERTIFICATE-----\n" +
+            "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx\n" +
+            "GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds\n" +
+            "b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV\n" +
+            "BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD\n" +
+            "VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa\n" +
+            "DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc\n" +
+            "THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb\n" +
+            "Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP\n" +
+            "c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX\n" +
+            "gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" +
+            "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF\n" +
+            "AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj\n" +
+            "Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG\n" +
+            "j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH\n" +
+            "hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC\n" +
+            "X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n" +
+            "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_PUBLIC_CERT = loadCertificate(CA_PUBLIC_CERT_STRING);
+
     private static final String CLIENT_CERT_STR = "-----BEGIN CERTIFICATE-----\n" +
             "MIIE/DCCAuQCAQEwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxCzAJBgNV\n" +
             "BAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0aW5n\n" +
diff --git a/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java b/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java
index 0304fda..feafd43 100644
--- a/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java
@@ -20,8 +20,6 @@
 import android.net.wifi.hotspot2.omadm.PpsMoParser;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
-import android.net.wifi.hotspot2.pps.Policy;
-import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.test.AndroidTestCase;
 
 import java.io.BufferedReader;
@@ -72,58 +70,6 @@
         Arrays.fill(certFingerprint, (byte) 0x1f);
 
         PasspointConfiguration config = new PasspointConfiguration();
-        config.setUpdateIdentifier(12);
-        assertEquals(12, config.getUpdateIdentifier());
-        config.setCredentialPriority(99);
-        assertEquals(99, config.getCredentialPriority());
-
-        // AAA Server trust root.
-        Map<String, byte[]> trustRootCertList = new HashMap<>();
-        trustRootCertList.put("server1.trust.root.com", certFingerprint);
-        config.setTrustRootCertList(trustRootCertList);
-        assertEquals(trustRootCertList, config.getTrustRootCertList());
-
-        // Subscription update.
-        UpdateParameter subscriptionUpdate = new UpdateParameter();
-        subscriptionUpdate.setUpdateIntervalInMinutes(120);
-        assertEquals(120, subscriptionUpdate.getUpdateIntervalInMinutes());
-        subscriptionUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_SSP);
-        assertEquals(UpdateParameter.UPDATE_METHOD_SSP, subscriptionUpdate.getUpdateMethod());
-        subscriptionUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER);
-        assertEquals(UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER,
-                subscriptionUpdate.getRestriction());
-        subscriptionUpdate.setServerUri("subscription.update.com");
-        assertEquals("subscription.update.com", subscriptionUpdate.getServerUri());
-        subscriptionUpdate.setUsername("subscriptionUser");
-        assertEquals("subscriptionUser", subscriptionUpdate.getUsername());
-        subscriptionUpdate.setBase64EncodedPassword("subscriptionPass");
-        assertEquals("subscriptionPass", subscriptionUpdate.getBase64EncodedPassword());
-        subscriptionUpdate.setTrustRootCertUrl("subscription.update.cert.com");
-        assertEquals("subscription.update.cert.com", subscriptionUpdate.getTrustRootCertUrl());
-        subscriptionUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
-        assertTrue(Arrays.equals(certFingerprint,
-                subscriptionUpdate.getTrustRootCertSha256Fingerprint()));
-        config.setSubscriptionUpdate(subscriptionUpdate);
-        assertEquals(subscriptionUpdate, config.getSubscriptionUpdate());
-
-        // Subscription parameters.
-        config.setSubscriptionCreationTimeInMillis(format.parse("2016-02-01T10:00:00Z").getTime());
-        assertEquals(format.parse("2016-02-01T10:00:00Z").getTime(),
-                config.getSubscriptionCreationTimeInMillis());
-        config.setSubscriptionExpirationTimeInMillis(format.parse("2016-03-01T10:00:00Z").getTime());
-        assertEquals(format.parse("2016-03-01T10:00:00Z").getTime(),
-                config.getSubscriptionExpirationTimeInMillis());
-        config.setSubscriptionType("Gold");
-        assertEquals("Gold", config.getSubscriptionType());
-        config.setUsageLimitDataLimit(921890);
-        assertEquals(921890, config.getUsageLimitDataLimit());
-        config.setUsageLimitStartTimeInMillis(format.parse("2016-12-01T10:00:00Z").getTime());
-        assertEquals(format.parse("2016-12-01T10:00:00Z").getTime(),
-                config.getUsageLimitStartTimeInMillis());
-        config.setUsageLimitTimeLimitInMinutes(120);
-        assertEquals(120, config.getUsageLimitTimeLimitInMinutes());
-        config.setUsageLimitUsageTimePeriodInMinutes(99910);
-        assertEquals(99910, config.getUsageLimitUsageTimePeriodInMinutes());
 
         // HomeSP configuration.
         HomeSp homeSp = new HomeSp();
@@ -134,46 +80,18 @@
         homeSp.setRoamingConsortiumOis(new long[] {0x112233L, 0x445566L});
         assertTrue(Arrays.equals(new long[] {0x112233L, 0x445566L},
                 homeSp.getRoamingConsortiumOis()));
-        homeSp.setIconUrl("icon.test.com");
-        assertEquals("icon.test.com", homeSp.getIconUrl());
-        Map<String, Long> homeNetworkIds = new HashMap<>();
-        homeNetworkIds.put("TestSSID", 0x12345678L);
-        homeNetworkIds.put("NullHESSID", null);
-        homeSp.setHomeNetworkIds(homeNetworkIds);
-        assertEquals(homeNetworkIds, homeSp.getHomeNetworkIds());
-        homeSp.setMatchAllOis(new long[] {0x11223344});
-        assertTrue(Arrays.equals(new long[] {0x11223344}, homeSp.getMatchAllOis()));
-        homeSp.setMatchAnyOis(new long[] {0x55667788});
-        assertTrue(Arrays.equals(new long[] {0x55667788}, homeSp.getMatchAnyOis()));
-        homeSp.setOtherHomePartners(new String[] {"other.fqdn.com"});
-        assertTrue(Arrays.equals(new String[] {"other.fqdn.com"},
-                homeSp.getOtherHomePartners()));
         config.setHomeSp(homeSp);
         assertEquals(homeSp, config.getHomeSp());
 
         // Credential configuration.
         Credential credential = new Credential();
-        credential.setCreationTimeInMillis(format.parse("2016-01-01T10:00:00Z").getTime());
-        assertEquals(format.parse("2016-01-01T10:00:00Z").getTime(),
-                credential.getCreationTimeInMillis());
-        credential.setExpirationTimeInMillis(format.parse("2016-02-01T10:00:00Z").getTime());
-        assertEquals(format.parse("2016-02-01T10:00:00Z").getTime(),
-                credential.getExpirationTimeInMillis());
         credential.setRealm("shaken.stirred.com");
         assertEquals("shaken.stirred.com", credential.getRealm());
-        credential.setCheckAaaServerCertStatus(true);
-        assertTrue(credential.getCheckAaaServerCertStatus());
         Credential.UserCredential userCredential = new Credential.UserCredential();
         userCredential.setUsername("james");
         assertEquals("james", userCredential.getUsername());
         userCredential.setPassword("Ym9uZDAwNw==");
         assertEquals("Ym9uZDAwNw==", userCredential.getPassword());
-        userCredential.setMachineManaged(true);
-        assertTrue(userCredential.getMachineManaged());
-        userCredential.setSoftTokenApp("TestApp");
-        assertEquals("TestApp", userCredential.getSoftTokenApp());
-        userCredential.setAbleToShare(true);
-        assertTrue(userCredential.getAbleToShare());
         userCredential.setEapType(21);
         assertEquals(21, userCredential.getEapType());
         userCredential.setNonEapInnerMethod("MS-CHAP-V2");
@@ -196,70 +114,6 @@
         assertEquals(simCredential, credential.getSimCredential());
         config.setCredential(credential);
         assertEquals(credential, config.getCredential());
-
-        // Policy configuration.
-        Policy policy = new Policy();
-        List<Policy.RoamingPartner> preferredRoamingPartnerList = new ArrayList<>();
-        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
-        partner1.setFqdn("test1.fqdn.com");
-        assertEquals("test1.fqdn.com", partner1.getFqdn());
-        partner1.setFqdnExactMatch(true);
-        assertTrue(partner1.getFqdnExactMatch());
-        partner1.setPriority(127);
-        assertEquals(127, partner1.getPriority());
-        partner1.setCountries("us,fr");
-        assertEquals("us,fr", partner1.getCountries());
-        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
-        partner2.setFqdn("test2.fqdn.com");
-        assertEquals("test2.fqdn.com", partner2.getFqdn());
-        partner2.setFqdnExactMatch(false);
-        assertFalse(partner2.getFqdnExactMatch());
-        partner2.setPriority(200);
-        assertEquals(200, partner2.getPriority());
-        partner2.setCountries("*");
-        assertEquals("*", partner2.getCountries());
-        preferredRoamingPartnerList.add(partner1);
-        preferredRoamingPartnerList.add(partner2);
-        policy.setPreferredRoamingPartnerList(preferredRoamingPartnerList);
-        assertEquals(preferredRoamingPartnerList, policy.getPreferredRoamingPartnerList());
-        policy.setMinHomeDownlinkBandwidth(23412);
-        assertEquals(23412, policy.getMinHomeDownlinkBandwidth());
-        policy.setMinHomeUplinkBandwidth(9823);
-        assertEquals(9823, policy.getMinHomeUplinkBandwidth());
-        policy.setMinRoamingDownlinkBandwidth(9271);
-        assertEquals(9271, policy.getMinRoamingDownlinkBandwidth());
-        policy.setMinRoamingUplinkBandwidth(2315);
-        assertEquals(2315, policy.getMinRoamingUplinkBandwidth());
-        policy.setExcludedSsidList(new String[] {"excludeSSID"});
-        assertTrue(Arrays.equals(new String[] {"excludeSSID"}, policy.getExcludedSsidList()));
-        Map<Integer, String> requiredProtoPortMap = new HashMap<>();
-        requiredProtoPortMap.put(12, "34,92,234");
-        policy.setRequiredProtoPortMap(requiredProtoPortMap);
-        assertEquals(requiredProtoPortMap, policy.getRequiredProtoPortMap());
-        policy.setMaximumBssLoadValue(23);
-        assertEquals(23, policy.getMaximumBssLoadValue());
-        UpdateParameter policyUpdate = new UpdateParameter();
-        policyUpdate.setUpdateIntervalInMinutes(120);
-        assertEquals(120, policyUpdate.getUpdateIntervalInMinutes());
-        policyUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_OMADM);
-        assertEquals(UpdateParameter.UPDATE_METHOD_OMADM, policyUpdate.getUpdateMethod());
-        policyUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_HOMESP);
-        assertEquals(UpdateParameter.UPDATE_RESTRICTION_HOMESP, policyUpdate.getRestriction());
-        policyUpdate.setServerUri("policy.update.com");
-        assertEquals("policy.update.com", policyUpdate.getServerUri());
-        policyUpdate.setUsername("updateUser");
-        assertEquals("updateUser", policyUpdate.getUsername());
-        policyUpdate.setBase64EncodedPassword("updatePass");
-        assertEquals("updatePass", policyUpdate.getBase64EncodedPassword());
-        policyUpdate.setTrustRootCertUrl("update.cert.com");
-        assertEquals("update.cert.com", policyUpdate.getTrustRootCertUrl());
-        policyUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
-        assertTrue(Arrays.equals(certFingerprint,
-                policyUpdate.getTrustRootCertSha256Fingerprint()));
-        policy.setPolicyUpdate(policyUpdate);
-        assertEquals(policyUpdate, policy.getPolicyUpdate());
-        config.setPolicy(policy);
-        assertEquals(policy, config.getPolicy());
         return config;
     }
 
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
index e888c14..33c184a 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -188,6 +188,7 @@
     private void startScan() throws Exception {
         synchronized (mMySync) {
             mMySync.expectedState = STATE_SCANNING;
+            mScanResults = null;
             assertTrue(mWifiManager.startScan());
             long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
             while (System.currentTimeMillis() < timeout && mMySync.expectedState == STATE_SCANNING)
@@ -239,10 +240,17 @@
         assertTrue(mWifiManager.reconnect());
         assertTrue(mWifiManager.reassociate());
         assertTrue(mWifiManager.disconnect());
-        startScan();
         setWifiEnabled(false);
+        startScan();
         Thread.sleep(DURATION);
-        assertTrue(mWifiManager.isScanAlwaysAvailable());
+        if (mWifiManager.isScanAlwaysAvailable()) {
+            // Make sure at least one AP is found.
+            assertNotNull("mScanResult should not be null!", mScanResults);
+            assertFalse("empty scan results!", mScanResults.isEmpty());
+        } else {
+            // Make sure no scan results are available.
+            assertNull("mScanResult should be null!", mScanResults);
+        }
         final String TAG = "Test";
         assertNotNull(mWifiManager.createWifiLock(TAG));
         assertNotNull(mWifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG));
@@ -586,15 +594,12 @@
     private PasspointConfiguration generatePasspointConfig(Credential credential) {
         PasspointConfiguration config = new PasspointConfiguration();
         config.setCredential(credential);
-        // Setting update identifier to indicate R2 configuration, to avoid CA
-        // certificate being verified, since we're using a fake CA certificate
-        // for testing.
-        config.setUpdateIdentifier(1);
 
         // Setup HomeSp.
         HomeSp homeSp = new HomeSp();
         homeSp.setFqdn("Test.com");
         homeSp.setFriendlyName("Test Provider");
+        homeSp.setRoamingConsortiumOis(new long[] {0x11223344});
         config.setHomeSp(homeSp);
 
         return config;
@@ -614,7 +619,7 @@
         userCred.setPassword("password");
         userCred.setNonEapInnerMethod("PAP");
         credential.setUserCredential(userCred);
-        credential.setCaCertificate(FakeKeys.CA_CERT0);
+        credential.setCaCertificate(FakeKeys.CA_PUBLIC_CERT);
         return credential;
     }
 
@@ -631,7 +636,7 @@
         certCredential.setCertSha256Fingerprint(
                 MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
         credential.setCertCredential(certCredential);
-        credential.setCaCertificate(FakeKeys.CA_CERT0);
+        credential.setCaCertificate(FakeKeys.CA_PUBLIC_CERT);
         credential.setClientCertificateChain(new X509Certificate[] {FakeKeys.CLIENT_CERT});
         credential.setClientPrivateKey(FakeKeys.RSA_KEY1);
         return credential;
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 3b281a6..e547c13 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1314,6 +1314,13 @@
     <permission android:name="android.permission.NETWORK_STACK"
                 android:protectionLevel="signature" />
 
+    <!-- Allows Settings and SystemUI to call methods in Networking services
+         <p>Not for use by third-party or privileged applications.
+         @hide This should only be used by Settings and SystemUI.
+    -->
+    <permission android:name="android.permission.NETWORK_SETTINGS"
+        android:protectionLevel="signature" />
+
     <!-- ======================================= -->
     <!-- Permissions for short range, peripheral networks -->
     <!-- ======================================= -->
diff --git a/tests/tests/security/res/raw/bug_22771132.mp4 b/tests/tests/security/res/raw/bug_22771132.mp4
new file mode 100644
index 0000000..f97e1f3
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_22771132.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_36215950.mp4 b/tests/tests/security/res/raw/bug_36215950.mp4
new file mode 100644
index 0000000..a58f49e
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_36215950.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_36816007.mp4 b/tests/tests/security/res/raw/bug_36816007.mp4
new file mode 100644
index 0000000..70fa650
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_36816007.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_36895511.mp4 b/tests/tests/security/res/raw/bug_36895511.mp4
new file mode 100644
index 0000000..298494b
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_36895511.mp4
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 300d170..1642679 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -45,6 +45,7 @@
 import android.view.Surface;
 import android.webkit.cts.CtsTestServer;
 
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -74,6 +75,10 @@
      ***********************************************************/
 
     @SecurityTest
+    public void testStagefright_bug_22771132() throws Exception {
+        doStagefrightTest(R.raw.bug_22771132);
+    }
+
     public void testStagefright_bug_21443020() throws Exception {
         doStagefrightTest(R.raw.bug_21443020_webm);
     }
@@ -747,4 +752,152 @@
         thr.stopLooper();
         thr.join();
     }
+
+    public void testBug36215950() throws Exception {
+        doStagefrightTestRawBlob(R.raw.bug_36215950, "video/hevc", 320, 240);
+    }
+
+    public void testBug36816007() throws Exception {
+        doStagefrightTestRawBlob(R.raw.bug_36816007, "video/avc", 320, 240);
+    }
+
+    public void testBug36895511() throws Exception {
+        doStagefrightTestRawBlob(R.raw.bug_36895511, "video/hevc", 320, 240);
+    }
+
+    private void runWithTimeout(Runnable runner, int timeout) {
+        Thread t = new Thread(runner);
+        t.start();
+        try {
+            t.join(timeout);
+        } catch (InterruptedException e) {
+            fail("operation was interrupted");
+        }
+        if (t.isAlive()) {
+            fail("operation not completed within timeout of " + timeout + "ms");
+        }
+    }
+
+    private void releaseCodec(final MediaCodec codec) {
+        runWithTimeout(new Runnable() {
+            @Override
+            public void run() {
+                codec.release();
+            }
+        }, 5000);
+    }
+
+    private void doStagefrightTestRawBlob(int rid, String mime, int initWidth, int initHeight) throws Exception {
+
+        final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
+        final Context context = getInstrumentation().getContext();
+        final Resources resources =  context.getResources();
+
+        LooperThread thr = new LooperThread(new Runnable() {
+            @Override
+            public void run() {
+
+                MediaPlayer mp = new MediaPlayer();
+                mp.setOnErrorListener(mpcl);
+                AssetFileDescriptor fd = null;
+                try {
+                    fd = resources.openRawResourceFd(R.raw.good);
+
+                    // the onErrorListener won't receive MEDIA_ERROR_SERVER_DIED until
+                    // setDataSource has been called
+                    mp.setDataSource(fd.getFileDescriptor(),
+                                     fd.getStartOffset(),
+                                     fd.getLength());
+                    fd.close();
+                } catch (Exception e) {
+                    // this is a known-good file, so no failure should occur
+                    fail("setDataSource of known-good file failed");
+                }
+
+                synchronized(mpcl) {
+                    mpcl.notify();
+                }
+                Looper.loop();
+                mp.release();
+            }
+        });
+        thr.start();
+        // wait until the thread has initialized the MediaPlayer
+        synchronized(mpcl) {
+            mpcl.wait();
+        }
+
+        AssetFileDescriptor fd = resources.openRawResourceFd(rid);
+        byte [] blob = new byte[(int)fd.getLength()];
+        FileInputStream fis = fd.createInputStream();
+        int numRead = fis.read(blob);
+        fis.close();
+        //Log.i("@@@@", "read " + numRead + " bytes");
+
+        // find all the available decoders for this format
+        ArrayList<String> matchingCodecs = new ArrayList<String>();
+        int numCodecs = MediaCodecList.getCodecCount();
+        for (int i = 0; i < numCodecs; i++) {
+            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+            if (info.isEncoder()) {
+                continue;
+            }
+            try {
+                MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                if (caps != null) {
+                    matchingCodecs.add(info.getName());
+                }
+            } catch (IllegalArgumentException e) {
+                // type is not supported
+            }
+        }
+
+        if (matchingCodecs.size() == 0) {
+            Log.w(TAG, "no codecs for mime type " + mime);
+        }
+        String rname = resources.getResourceEntryName(rid);
+        // decode this blob once with each matching codec
+        for (String codecName: matchingCodecs) {
+            Log.i(TAG, "Decoding blob " + rname + " using codec " + codecName);
+            MediaCodec codec = MediaCodec.createByCodecName(codecName);
+            MediaFormat format = MediaFormat.createVideoFormat(mime, initWidth, initHeight);
+            codec.configure(format, null, null, 0);
+            codec.start();
+
+            try {
+                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+                ByteBuffer [] inputBuffers = codec.getInputBuffers();
+                // enqueue the bad data a number of times, in case
+                // the codec needs multiple buffers to fail.
+                for(int i = 0; i < 64; i++) {
+                    int bufidx = codec.dequeueInputBuffer(5000);
+                    if (bufidx >= 0) {
+                        Log.i(TAG, "got input buffer of size " + inputBuffers[bufidx].capacity());
+                        inputBuffers[bufidx].rewind();
+                        inputBuffers[bufidx].put(blob, 0, numRead);
+                        codec.queueInputBuffer(bufidx, 0, numRead, 0, 0);
+                    } else {
+                        Log.i(TAG, "no input buffer");
+                    }
+                    bufidx = codec.dequeueOutputBuffer(info, 5000);
+                    if (bufidx >= 0) {
+                        Log.i(TAG, "got output buffer");
+                        codec.releaseOutputBuffer(bufidx, false);
+                    } else {
+                        Log.i(TAG, "no output buffer");
+                    }
+                }
+            } catch (Exception e) {
+                // ignore, not a security issue
+            } finally {
+                releaseCodec(codec);
+            }
+        }
+
+        String cve = rname.replace("_", "-").toUpperCase();
+        assertFalse("Device *IS* vulnerable to " + cve,
+                    mpcl.waitForError() == MediaPlayer.MEDIA_ERROR_SERVER_DIED);
+        thr.stopLooper();
+        thr.join();
+    }
 }
diff --git a/tests/tests/text/src/android/text/cts/FontCoverageTest.java b/tests/tests/text/src/android/text/cts/FontCoverageTest.java
new file mode 100644
index 0000000..68c9afa
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/FontCoverageTest.java
@@ -0,0 +1,1233 @@
+/*
+ * 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.text.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.icu.lang.UCharacter;
+import android.icu.lang.UCharacterCategory;
+import android.icu.text.UnicodeSet;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class FontCoverageTest {
+    // All characters in ASCII, Latin 1, Latin Extended A to D, and Currency Symbols block, as well
+    // as all Latin, Greek and Cyrillic characters. Limited to Unicode 7.0.
+    private static final UnicodeSet MIN_LGC = new UnicodeSet(
+            "[[[:Block=ASCII:]"
+            + "[:Block=Latin_1:]"
+            + "[:Block=Latin_Extended_A:]"
+            + "[:Block=Latin_Extended_B:]"
+            + "[:Block=Latin_Extended_C:]"
+            + "[:Block=Latin_Extended_D:]"
+            + "[:Block=Currency_Symbols:]"
+            + "[:Script=Latin:]"
+            + "[:Script=Greek:]"
+            + "[:Script=Cyrillic:]]"
+            + "&[:Age=7.0:]"
+            // Skip the blocks for ancient numbers and symbols.
+            + "&[:^Block=Ancient_Greek_Numbers:]&[:^Block=Ancient_Symbols:]]");
+
+    private static final UnicodeSet MIN_EXTRAS = new UnicodeSet(
+            "[\u0301" // COMBINING ACUTE ACCENT
+            + "\u2010" // HYPHEN
+            + "\u2013" // EN DASH
+            + "\u2014" // EM DASH
+            + "\u2018" // LEFT SINGLE QUOTATION MARK
+            + "\u2019" // RIGHT SINGLE QUOTATION MARK
+            + "\u201A" // SINGLE LOW-9 QUOTATION MARK
+            + "\u201C" // LEFT DOUBLE QUOTATION MARK
+            + "\u201D" // RIGHT DOUBLE QUOTATION MARK
+            + "\u201E" // DOUBLE LOW-9 QUOTATION MARK
+            + "\u2020" // DAGGER
+            + "\u2021" // DOUBLE DAGGER
+            + "\u2026" // HORIZONTAL ELLIPSIS
+            + "\u2032" // PRIME
+            + "\u2033" // DOUBLE PRIME
+            + "\u2212]"); // MINUS SIGN
+
+    private static final UnicodeSet MIN_COVERAGE = new UnicodeSet(MIN_LGC).addAll(MIN_EXTRAS);
+
+    // Characters outside of MIN_COVERAGE that are needed for some locales.
+    private static final HashMap<String, UnicodeSet> EXEMPLAR_MAP = new HashMap();
+    static {
+        EXEMPLAR_MAP.put("agq", new UnicodeSet("[{\u0254\u0300}{\u0254\u0302}{\u0254\u0304}"
+                + "{\u0254\u030C}{\u025B\u0300}{\u025B\u0302}{\u025B\u0304}{\u025B\u030C}"
+                + "{\u0268\u0300}{\u0268\u0302}{\u0268\u0304}{\u0268\u030C}{\u0289\u0300}"
+                + "{\u0289\u0302}{\u0289\u0304}{\u0289\u030C}]"));
+        EXEMPLAR_MAP.put("am", new UnicodeSet("[\u1200-\u1206\u1208-\u1246\u1248\u124A-\u124D"
+                + "\u1260-\u1286\u1288\u128A-\u128D\u1290-\u12AE\u12B0\u12B2-\u12B5\u12B8-\u12BE"
+                + "\u12C8-\u12CE\u12D0-\u12D6\u12D8-\u12EE\u12F0-\u12F7\u1300-\u130E\u1310"
+                + "\u1312-\u1315\u1320-\u1346\u1348-\u1357\u1361-\u1366\u2039\u203A]"));
+        EXEMPLAR_MAP.put("ar", new UnicodeSet("[\u060C\u061B\u061F\u0621-\u063A\u0641-\u0652"
+                + "\u0660-\u066C\u066F\u0670\u067E\u0686\u0698\u069C\u06A2\u06A4\u06A5"
+                + "\u06A7-\u06A9\u06AF\u06CC]"));
+        EXEMPLAR_MAP.put("ar-DZ", new UnicodeSet("[\u060C\u061B\u061F\u0621-\u063A\u0641-\u0652"
+                + "\u066F\u0670\u067E\u0686\u0698\u069C\u06A2\u06A4\u06A5\u06A7-\u06A9\u06AF"
+                + "\u06CC]"));
+        EXEMPLAR_MAP.put("ar-EH", new UnicodeSet("[\u060C\u061B\u061F\u0621-\u063A\u0641-\u0652"
+                + "\u066F\u0670\u067E\u0686\u0698\u069C\u06A2\u06A4\u06A5\u06A7-\u06A9\u06AF"
+                + "\u06CC]"));
+        EXEMPLAR_MAP.put("ar-LY", new UnicodeSet("[\u060C\u061B\u061F\u0621-\u063A\u0641-\u0652"
+                + "\u066F\u0670\u067E\u0686\u0698\u069C\u06A2\u06A4\u06A5\u06A7-\u06A9\u06AF"
+                + "\u06CC]"));
+        EXEMPLAR_MAP.put("ar-MA", new UnicodeSet("[\u060C\u061B\u061F\u0621-\u063A\u0641-\u0652"
+                + "\u066F\u0670\u067E\u0686\u0698\u069C\u06A2\u06A4\u06A5\u06A7-\u06A9\u06AD\u06AF"
+                + "\u06CC\u0763]"));
+        EXEMPLAR_MAP.put("ar-TN", new UnicodeSet("[\u060C\u061B\u061F\u0621-\u063A\u0641-\u0652"
+                + "\u066F\u0670\u067E\u0686\u0698\u069C\u06A2\u06A4\u06A5\u06A7-\u06A9\u06AF"
+                + "\u06CC]"));
+        EXEMPLAR_MAP.put("as", new UnicodeSet("[\u0981-\u0983\u0985-\u098B\u098F\u0990"
+                + "\u0993-\u09A8\u09AA-\u09AF\u09B2\u09B6-\u09B9\u09BC\u09BE-\u09C3\u09C7\u09C8"
+                + "\u09CB-\u09CD\u09E6-\u09F2{\u0995\u09CD\u09B7}{\u09A1\u09BC}"
+                + "{\u09A2\u09BC}{\u09AF\u09BC}]"));
+        EXEMPLAR_MAP.put("bas", new UnicodeSet("[{a\u1DC6}{a\u1DC7}{e\u1DC6}{e\u1DC7}{i\u1DC6}"
+                + "{i\u1DC7}{o\u1DC6}{o\u1DC7}{u\u1DC6}{u\u1DC7}{\u0254\u0300}{\u0254\u0302}"
+                + "{\u0254\u0304}{\u0254\u030C}{\u0254\u1DC6}{\u0254\u1DC7}{\u025B\u0300}"
+                + "{\u025B\u0302}{\u025B\u0304}{\u025B\u030C}{\u025B\u1DC6}{\u025B\u1DC7}]"));
+        EXEMPLAR_MAP.put("bg", new UnicodeSet("[\u2116{\u0430\u0300}{\u043E\u0300}{\u0443\u0300}"
+                + "{\u044A\u0300}{\u044E\u0300}{\u044F\u0300}]"));
+        EXEMPLAR_MAP.put("bn", new UnicodeSet("[\u0981-\u0983\u0985-\u098C\u098F\u0990"
+                + "\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8"
+                + "\u09CB-\u09CE\u09D7\u09E0-\u09E3\u09E6-\u09FA{\u0995\u09CD\u09B7}{\u09A1\u09BC}"
+                + "{\u09A2\u09BC}{\u09AF\u09BC}]"));
+        EXEMPLAR_MAP.put("bo", new UnicodeSet("[\u0F00\u0F40-\u0F42\u0F44-\u0F47\u0F49-\u0F4C"
+                + "\u0F4E-\u0F51\u0F53-\u0F56\u0F58-\u0F5B\u0F5D-\u0F68\u0F6A\u0F72\u0F74\u0F77"
+                + "\u0F79-\u0F80\u0F84\u0F90-\u0F92\u0F94-\u0F97\u0F99-\u0F9C\u0F9E-\u0FA1"
+                + "\u0FA3-\u0FA6\u0FA8-\u0FAB\u0FAD-\u0FB8\u0FBA-\u0FBC{\u0F40\u0FB5}{\u0F42\u0FB7}"
+                + "{\u0F4C\u0FB7}{\u0F51\u0FB7}{\u0F56\u0FB7}{\u0F5B\u0FB7}{\u0F71\u0F72}"
+                + "{\u0F71\u0F74}{\u0F71\u0F80}{\u0F90\u0FB5}{\u0F92\u0FB7}{\u0F9C\u0FB7}"
+                + "{\u0FA1\u0FB7}{\u0FA6\u0FB7}{\u0FAB\u0FB7}{\u0FB2\u0F80}{\u0FB3\u0F80}]"));
+        EXEMPLAR_MAP.put("br", new UnicodeSet("[{c\u02BCh}]"));
+        EXEMPLAR_MAP.put("brx", new UnicodeSet("[\u0901\u0902\u0905-\u090A\u090D\u090F-\u0911"
+                + "\u0913-\u0918\u091A-\u0928\u092A-\u0930\u0932\u0933\u0935-\u0939\u093C"
+                + "\u093E-\u0943\u0945\u0947-\u0949\u094B-\u094D{\u0921\u093C}]"));
+        EXEMPLAR_MAP.put("chr", new UnicodeSet("[\u13A0\u13A6\u13AD\u13B3\u13B9\u13BE\u13C6\u13CC"
+                + "\u13D3\u13DC\u13E3\u13E9\u13EF\u13F8-\u13FC\uAB70-\uABBF]"));
+        EXEMPLAR_MAP.put("dz", new UnicodeSet("[\u0F04-\u0F06\u0F08-\u0F0A\u0F0C-\u0F12\u0F14"
+                + "\u0F20-\u0F29\u0F34\u0F36\u0F3C\u0F3D\u0F40-\u0F42\u0F44-\u0F47\u0F49-\u0F4C"
+                + "\u0F4E-\u0F51\u0F53-\u0F56\u0F58-\u0F5B\u0F5D-\u0F68\u0F72\u0F74\u0F7A-\u0F7E"
+                + "\u0F80\u0F84\u0F90-\u0F92\u0F94\u0F97\u0F99-\u0F9C\u0F9E-\u0FA1\u0FA3-\u0FA6"
+                + "\u0FA8-\u0FAB\u0FAD\u0FB1-\u0FB3\u0FB5-\u0FB7\u0FBA-\u0FBC\u0FBE\u0FBF"
+                + "\u0FD0-\u0FD4]"));
+        EXEMPLAR_MAP.put("ee", new UnicodeSet("[{\u0254\u0300}{\u0254\u0303}{\u025B\u0300}"
+                + "{\u025B\u0303}]"));
+        EXEMPLAR_MAP.put("ewo", new UnicodeSet("[{\u0254\u0300}{\u0254\u0302}{\u0254\u030C}"
+                + "{\u0259\u0300}{\u0259\u0302}{\u0259\u030C}{\u025B\u0300}{\u025B\u0302}"
+                + "{\u025B\u030C}]"));
+        EXEMPLAR_MAP.put("fa-AF", new UnicodeSet("[\u060C\u061B\u061F\u0621-\u063A\u0641-\u0652"
+                + "\u0654\u0656\u066A-\u066C\u0670\u067C\u067E\u0681\u0685\u0686\u0689\u0693\u0696"
+                + "\u0698\u069A\u06A9\u06AB\u06AF\u06BC\u06CC\u06F0-\u06F9\u2039\u203A]"));
+        EXEMPLAR_MAP.put("fa-IR", new UnicodeSet("[\u060C\u061B\u061F\u0621-\u063A\u0641-\u0652"
+                + "\u0654\u0656\u066A-\u066C\u0670\u067E\u0686\u0698\u06A9\u06AF\u06CC\u06F0-\u06F9"
+                + "\u2039\u203A{\u200E\u066A}]"));
+        EXEMPLAR_MAP.put("gu", new UnicodeSet("[\u0A81-\u0A83\u0A85-\u0A8B\u0A8D\u0A8F-\u0A91"
+                + "\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9"
+                + "\u0ACB-\u0ACD\u0AD0\u0AE0\u0AF0{\u0A85\u0A82}{\u0A85\u0A83}"
+                + "{\u0A95\u0ACD\u0AB7}{\u0A9C\u0ACD\u0A9E}{\u0AA4\u0ACD\u0AB0}]"));
+        EXEMPLAR_MAP.put("ha", new UnicodeSet("[\u02BC{r\u0303}{\u02BCY}{\u02BCy}]"));
+        EXEMPLAR_MAP.put("haw", new UnicodeSet("[\u02BB]"));
+        EXEMPLAR_MAP.put("iw", new UnicodeSet("[\u05B0-\u05B9\u05BB-\u05BF\u05C1\u05C2\u05C4"
+                + "\u05D0-\u05EA\u05F3\u05F4]"));
+        EXEMPLAR_MAP.put("hi", new UnicodeSet("[\u0901-\u0903\u0905-\u090D\u090F-\u0911"
+                + "\u0913-\u0928\u092A-\u0930\u0932\u0933\u0935-\u0939\u093C-\u0945\u0947-\u0949"
+                + "\u094B-\u094D\u0950\u0970]"));
+        EXEMPLAR_MAP.put("hu", new UnicodeSet("[\u2052\u27E8\u27E9]"));
+        EXEMPLAR_MAP.put("hy", new UnicodeSet("[\u055A-\u055F\u0561-\u0587\u058A]"));
+        EXEMPLAR_MAP.put("ii", new UnicodeSet("[\uA000-\uA48C]"));
+        EXEMPLAR_MAP.put("jgo", new UnicodeSet("[\u2039\u203A{m\u0300}{m\u0304}{n\u0304}"
+                + "{\u014B\u0300}{\u014B\u0304}{\u0244\u0308}{\u0254\u0302}{\u0254\u030C}"
+                + "{\u025B\u0300}{\u025B\u0302}{\u025B\u0304}{\u025B\u030C}{\u0289\u0302}"
+                + "{\u0289\u0308}{\u0289\u030C}]"));
+        EXEMPLAR_MAP.put("ja", new UnicodeSet("[\u2015\u2016\u2025\u2030\u203B\u203E\u3001-\u3003"
+                + "\u3005\u3008-\u3011\u3014\u3015\u301C\u3041-\u3093\u309D\u309E\u30A1-\u30F6"
+                + "\u30FB-\u30FE\u4E00\u4E01\u4E03\u4E07-\u4E0B\u4E0D\u4E0E\u4E11\u4E14\u4E16\u4E18"
+                + "\u4E19\u4E21\u4E26\u4E2D\u4E38\u4E39\u4E3B\u4E45\u4E4F\u4E57"
+                + "\u4E59\u4E5D\u4E71\u4E73\u4E7E\u4E80\u4E86\u4E88\u4E89\u4E8B"
+                + "\u4E8C\u4E92\u4E94\u4E95\u4E9C\u4EA1\u4EA4\u4EA5\u4EA8\u4EAB-\u4EAD"
+                + "\u4EBA\u4EC1\u4ECA\u4ECB\u4ECF\u4ED5\u4ED6\u4ED8\u4ED9\u4EE3-\u4EE5"
+                + "\u4EEE\u4EF0\u4EF2\u4EF6\u4EFB\u4F01\u4F0A\u4F0F-\u4F11\u4F1A\u4F1D"
+                + "\u4F2F\u4F34\u4F38\u4F3A\u4F3C\u4F46\u4F4D-\u4F50\u4F53\u4F55"
+                + "\u4F59\u4F5C\u4F73\u4F75\u4F7F\u4F8B\u4F8D\u4F9B\u4F9D\u4FA1"
+                + "\u4FAE\u4FAF\u4FB5\u4FBF\u4FC2\u4FC3\u4FCA\u4FD7\u4FDD\u4FE1"
+                + "\u4FEE\u4FF3\u4FF5\u4FF8\u4FFA\u5009\u500B\u500D\u5012\u5019"
+                + "\u501F\u5023\u5024\u502B\u5039\u5049\u504F\u505C\u5065\u5074-\u5076"
+                + "\u507D\u508D\u5091\u5098\u5099\u50AC\u50B5\u50B7\u50BE\u50CD"
+                + "\u50CF\u50D5\u50DA\u50E7\u5100\u5104\u5112\u511F\u512A\u5143-\u5146"
+                + "\u5148\u5149\u514B-\u514E\u5150\u515A\u5165\u5168\u516B-\u516D"
+                + "\u5171\u5175\u5177\u5178\u517C\u5185\u5186\u518A\u518D\u5192"
+                + "\u5197\u5199\u51A0\u51AC\u51B7\u51C6\u51CD\u51DD\u51E1\u51E6"
+                + "\u51F6\u51F8-\u51FA\u5200\u5203\u5206-\u5208\u520A\u5211\u5217\u521D"
+                + "\u5224\u5225\u5229\u5230\u5236-\u5238\u523A\u523B\u5247\u524A"
+                + "\u524D\u5256\u525B\u5263\u5264\u526F\u5270\u5272\u5275\u5287"
+                + "\u529B\u529F\u52A0\u52A3\u52A9\u52AA\u52B1\u52B4\u52B9\u52BE"
+                + "\u52C5\u52C7\u52C9\u52D5\u52D8\u52D9\u52DD\u52DF\u52E2\u52E4"
+                + "\u52E7\u52F2\u52FA\u5301\u5305\u5316\u5317\u5320\u5339-\u533B"
+                + "\u533F\u5341\u5343\u5347\u5348\u534A\u5351-\u5354\u5357\u5358"
+                + "\u535A\u5360\u536F-\u5371\u5373-\u5375\u5378\u5384\u5398\u539A"
+                + "\u539F\u53B3\u53BB\u53C2\u53C8\u53CA-\u53CE\u53D4\u53D6\u53D7\u53D9"
+                + "\u53E3-\u53E5\u53EB\u53EC\u53EF\u53F0\u53F2\u53F3\u53F7\u53F8"
+                + "\u5404\u5408\u5409\u540C-\u5411\u541B\u541F\u5426\u542B\u5438\u5439"
+                + "\u5448-\u544A\u5468\u5473\u547C\u547D\u548C\u54B2\u54C0\u54C1"
+                + "\u54E1\u54F2\u5506\u5507\u5510\u552F\u5531\u5546\u554F\u5553"
+                + "\u5584\u559A\u559C\u559D\u55AA\u55AB\u55B6\u55E3\u5606\u5609"
+                + "\u5631\u5668\u5674\u5687\u56DA\u56DB\u56DE\u56E0\u56E3\u56F0"
+                + "\u56F2\u56F3\u56FA\u56FD\u570F\u5712\u571F\u5727\u5728\u5730"
+                + "\u5742\u5747\u574A\u5751\u576A\u5782\u578B\u57A3\u57CB\u57CE"
+                + "\u57DF\u57F7\u57F9\u57FA\u57FC\u5800\u5802\u5805\u5815\u5824"
+                + "\u582A\u5831\u5834\u5840\u5841\u584A\u5851\u5854\u5857\u585A"
+                + "\u5869\u587E\u5883\u5893\u5897\u589C\u58A8\u58B3\u58BE\u58C1"
+                + "\u58C7\u58CA\u58CC\u58EB\u58EC\u58EE\u58F0-\u58F2\u5909\u590F"
+                + "\u5915\u5916\u591A\u591C\u5922\u5927\u5929-\u592B\u592E\u5931"
+                + "\u5947-\u5949\u594F\u5951\u5954\u5965\u5968\u596A\u596E\u5973"
+                + "\u5974\u597D\u5982-\u5984\u598A\u5999\u59A5\u59A8\u59B9\u59BB"
+                + "\u59C9\u59CB\u59D3\u59D4\u59EB\u59FB\u59FF\u5A01\u5A18\u5A20"
+                + "\u5A2F\u5A46\u5A5A\u5A66\u5A7F\u5A92\u5AC1\u5ACC\u5AE1\u5B22"
+                + "\u5B50\u5B54\u5B57\u5B58\u5B5D\u5B63\u5B64\u5B66\u5B6B\u5B85"
+                + "\u5B87-\u5B89\u5B8C\u5B97-\u5B9A\u5B9C\u5B9D\u5B9F\u5BA2-\u5BA4"
+                + "\u5BAE\u5BB0\u5BB3-\u5BB6\u5BB9\u5BBF\u5BC2\u5BC4-\u5BC6\u5BCC\u5BD2"
+                + "\u5BDB\u5BDD\u5BDF\u5BE1\u5BE7\u5BE9\u5BEE\u5BF8\u5BFA\u5BFE"
+                + "\u5BFF\u5C01\u5C02\u5C04\u5C06\u5C09-\u5C0B\u5C0E\u5C0F\u5C11\u5C1A"
+                + "\u5C31\u5C3A\u5C3C-\u5C40\u5C45\u5C48\u5C4A\u5C4B\u5C55\u5C5E"
+                + "\u5C64\u5C65\u5C6F\u5C71\u5C90\u5CA1\u5CA9\u5CAC\u5CB3\u5CB8"
+                + "\u5CE0\u5CE1\u5CF0\u5CF6\u5D07\u5D0E\u5D29\u5DDD\u5DDE\u5DE1"
+                + "\u5DE3\u5DE5-\u5DE8\u5DEE\u5DF1\u5DF3\u5DFB\u5E02\u5E03\u5E06\u5E0C"
+                + "\u5E1D\u5E25\u5E2B\u5E2D\u5E2F\u5E30\u5E33\u5E38\u5E3D\u5E45"
+                + "\u5E55\u5E63\u5E72-\u5E74\u5E78\u5E79\u5E7B-\u5E7E\u5E81\u5E83"
+                + "\u5E8A\u5E8F\u5E95\u5E97\u5E9A\u5E9C\u5EA6\u5EA7\u5EAB\u5EAD"
+                + "\u5EB6-\u5EB8\u5EC3\u5EC9\u5ECA\u5EF6\u5EF7\u5EFA\u5F01\u5F0A"
+                + "\u5F0F\u5F10\u5F13-\u5F15\u5F18\u5F1F\u5F26\u5F27\u5F31\u5F35"
+                + "\u5F37\u5F3E\u5F53\u5F62\u5F69\u5F6B\u5F70\u5F71\u5F79\u5F7C"
+                + "\u5F80\u5F81\u5F84\u5F85\u5F8B\u5F8C\u5F90\u5F92\u5F93\u5F97"
+                + "\u5FA1\u5FA9\u5FAA\u5FAE\u5FB3\u5FB4\u5FB9\u5FC3\u5FC5\u5FCC"
+                + "\u5FCD\u5FD7-\u5FD9\u5FDC\u5FE0\u5FEB\u5FF5\u6012\u6016\u601D\u6020"
+                + "\u6025\u6027\u602A\u604B\u6050\u6052\u6065\u6068\u6069\u606D"
+                + "\u606F\u6075\u6094\u609F\u60A0\u60A3\u60A6\u60A9\u60AA\u60B2"
+                + "\u60BC\u60C5\u60D1\u60DC\u60E8\u60F0\u60F3\u6101\u6109\u610F"
+                + "\u611A\u611B\u611F\u6148\u614B\u614C\u614E\u6155\u6162\u6163"
+                + "\u6168\u616E\u6170\u6176\u6182\u618E\u61A4\u61A9\u61B2\u61B6"
+                + "\u61BE\u61C7\u61D0\u61F2\u61F8\u620A\u620C\u6210-\u6212\u6226\u622F"
+                + "\u6238\u623B\u623F\u6240\u6247\u6249\u624B\u624D\u6253\u6255"
+                + "\u6271\u6276\u6279\u627F\u6280\u6284\u628A\u6291\u6295\u6297"
+                + "\u6298\u629C\u629E\u62AB\u62B1\u62B5\u62B9\u62BC\u62BD\u62C5"
+                + "\u62CD\u62D0\u62D2\u62D3\u62D8\u62D9\u62DB\u62DD\u62E0\u62E1"
+                + "\u62EC\u62F7\u62FC\u62FE\u6301\u6307\u6311\u6319\u631F\u632F"
+                + "\u633F\u6355\u635C\u6368\u636E\u6383\u6388\u638C\u6392\u6398"
+                + "\u639B\u63A1\u63A2\u63A5\u63A7\u63A8\u63AA\u63B2\u63CF\u63D0"
+                + "\u63DA\u63DB\u63E1\u63EE\u63F4\u63FA\u640D\u642C\u642D\u643A"
+                + "\u643E\u6442\u6458\u6469\u6483\u64A4\u64AE\u64B2\u64C1\u64CD"
+                + "\u64E6\u64EC\u652F\u6539\u653B\u653E\u653F\u6545\u654F\u6551"
+                + "\u6557\u6559\u6562\u6563\u656C\u6570\u6574\u6575\u6577\u6587"
+                + "\u6589\u658E\u6597\u6599\u659C\u65A4\u65A5\u65AD\u65B0\u65B9"
+                + "\u65BD\u65C5\u65CB\u65CF\u65D7\u65E2\u65E5\u65E7-\u65E9\u65EC\u6606"
+                + "\u6607\u660C\u660E\u6613\u6614\u661F\u6620\u6625\u6628\u662D"
+                + "\u662F\u663C\u6642\u6669\u666E\u666F\u6674\u6676\u6681\u6687"
+                + "\u6691\u6696\u6697\u66A6\u66AB\u66AE\u66B4\u66C7\u66DC\u66F2"
+                + "\u66F4\u66F8\u66F9\u66FF\u6700\u6708\u6709\u670D\u6715\u6717"
+                + "\u671B\u671D\u671F\u6728\u672A-\u672D\u6731\u6734\u673A\u673D"
+                + "\u6749\u6750\u6751\u675F\u6761\u6765\u676F\u6771\u677E\u677F"
+                + "\u6790\u6797\u679A\u679C\u679D\u67A0\u67A2\u67AF\u67B6\u67C4"
+                + "\u67D0\u67D3\u67D4\u67F1\u67F3\u67FB\u6804\u6813\u6821\u682A"
+                + "\u6838\u6839\u683C\u683D\u6843\u6848\u6851\u685C\u685F\u6885"
+                + "\u68B0\u68C4\u68CB\u68D2\u68DA\u68DF\u68EE\u68FA\u690D\u691C"
+                + "\u6954\u696D\u6975\u697C\u697D\u6982\u69CB\u69D8\u69FD\u6A19"
+                + "\u6A21\u6A29\u6A2A\u6A39\u6A4B\u6A5F\u6B04\u6B20\u6B21\u6B27"
+                + "\u6B32\u6B3A\u6B3E\u6B4C\u6B53\u6B62\u6B63\u6B66\u6B69\u6B6F"
+                + "\u6B73\u6B74\u6B7B\u6B89-\u6B8B\u6B96\u6BB4\u6BB5\u6BBA\u6BBB\u6BBF"
+                + "\u6BCD\u6BCE\u6BD2\u6BD4\u6BDB\u6C0F\u6C11\u6C17\u6C34\u6C37"
+                + "\u6C38\u6C41\u6C42\u6C4E\u6C57\u6C5A\u6C5F\u6C60\u6C7A\u6C7D"
+                + "\u6C88\u6C96\u6CA1\u6CA2\u6CB3\u6CB8\u6CB9\u6CBB\u6CBC\u6CBF"
+                + "\u6CC1\u6CC9\u6CCA\u6CCC\u6CD5\u6CE1-\u6CE3\u6CE5\u6CE8\u6CF0\u6CF3"
+                + "\u6D0B\u6D17\u6D1E\u6D25\u6D2A\u6D3B\u6D3E\u6D41\u6D44\u6D45"
+                + "\u6D5C\u6D66\u6D6A\u6D6E\u6D74\u6D77\u6D78\u6D88\u6D99\u6DAF"
+                + "\u6DB2\u6DBC\u6DD1\u6DE1\u6DF1\u6DF7\u6DFB\u6E05\u6E07-\u6E09"
+                + "\u6E0B\u6E13\u6E1B\u6E21\u6E26\u6E29\u6E2C\u6E2F\u6E56\u6E6F"
+                + "\u6E7E-\u6E80\u6E90\u6E96\u6E9D\u6EB6\u6EC5\u6ECB\u6ED1\u6EDD"
+                + "\u6EDE\u6EF4\u6F01\u6F02\u6F06\u6F0F\u6F14\u6F20\u6F22\u6F2B"
+                + "\u6F2C\u6F38\u6F54\u6F5C\u6F5F\u6F64\u6F6E\u6F84\u6FC0\u6FC1"
+                + "\u6FC3\u6FEB\u6FEF\u702C\u706B\u706F\u7070\u707D\u7089\u708A"
+                + "\u708E\u70AD\u70B9\u70BA\u70C8\u7121\u7126\u7136\u713C\u7159"
+                + "\u7167\u7169\u716E\u719F\u71B1\u71C3\u71E5\u7206\u7235\u7236"
+                + "\u7247\u7248\u7259\u725B\u7267\u7269\u7272\u7279\u72A0\u72AC"
+                + "\u72AF\u72B6\u72C2\u72E9\u72EC\u72ED\u731B\u731F\u732A\u732B"
+                + "\u732E\u7336\u733F\u7344\u7363\u7372\u7384\u7387\u7389\u738B"
+                + "\u73CD\u73E0\u73ED\u73FE\u7403\u7406\u7434\u74B0\u74BD\u74F6"
+                + "\u7518\u751A\u751F\u7523\u7528\u7530-\u7533\u7537\u753A\u753B\u754C"
+                + "\u7551\u7554\u7559\u755C\u755D\u7565\u756A\u7570\u7573\u758E"
+                + "\u7591\u75AB\u75B2\u75BE\u75C5\u75C7\u75D8\u75DB\u75E2\u75F4"
+                + "\u7642\u7652\u7656\u7678\u767A\u767B\u767D\u767E\u7684\u7686"
+                + "\u7687\u76AE\u76BF\u76C6\u76CA\u76D7\u76DB\u76DF\u76E3\u76E4"
+                + "\u76EE\u76F2\u76F4\u76F8\u76FE\u7701\u770B\u770C\u771F\u7720"
+                + "\u773A\u773C\u7740\u7761\u7763\u77AC\u77DB\u77E2\u77E5\u77ED"
+                + "\u77EF\u77F3\u7802\u7814\u7815\u7832\u7834\u785D\u786B\u786C"
+                + "\u7881\u7891\u78BA\u78C1\u78E8\u7901\u790E\u793A\u793C\u793E"
+                + "\u7948\u7949\u7956\u795A\u795D\u795E\u7965\u7968\u796D\u7981"
+                + "\u7984\u7985\u798D-\u798F\u79C0\u79C1\u79CB\u79D1\u79D2\u79D8"
+                + "\u79DF\u79E9\u79F0\u79FB\u7A0B\u7A0E\u7A1A\u7A2E\u7A32\u7A3C"
+                + "\u7A3F\u7A40\u7A42\u7A4D\u7A4F\u7A6B\u7A74\u7A76\u7A7A\u7A81"
+                + "\u7A83\u7A92\u7A93\u7AAE\u7AAF\u7ACB\u7ADC\u7AE0\u7AE5\u7AEF"
+                + "\u7AF6\u7AF9\u7B11\u7B1B\u7B26\u7B2C\u7B46\u7B49\u7B4B\u7B52"
+                + "\u7B54\u7B56\u7B87\u7B97\u7BA1\u7BB1\u7BC0\u7BC4\u7BC9\u7BE4"
+                + "\u7C21\u7C3F\u7C4D\u7C73\u7C89\u7C8B\u7C92\u7C97\u7C98\u7C9B"
+                + "\u7CA7\u7CBE\u7CD6\u7CE7\u7CF8\u7CFB\u7CFE\u7D00\u7D04\u7D05"
+                + "\u7D0B\u7D0D\u7D14\u7D19-\u7D1B\u7D20-\u7D22\u7D2B\u7D2F\u7D30\u7D33"
+                + "\u7D39\u7D3A\u7D42\u7D44\u7D4C\u7D50\u7D5E\u7D61\u7D66\u7D71"
+                + "\u7D75\u7D76\u7D79\u7D99\u7D9A\u7DAD\u7DB1\u7DB2\u7DBF\u7DCA"
+                + "\u7DCF\u7DD1\u7DD2\u7DDA\u7DE0\u7DE8\u7DE9\u7DEF\u7DF4\u7E01"
+                + "\u7E04\u7E1B\u7E26\u7E2B\u7E2E\u7E3E\u7E41\u7E4A\u7E54\u7E55"
+                + "\u7E6D\u7E70\u7F36\u7F6A\u7F6E\u7F70\u7F72\u7F77\u7F85\u7F8A"
+                + "\u7F8E\u7FA4\u7FA9\u7FBD\u7FC1\u7FCC\u7FD2\u7FFB\u7FFC\u8001"
+                + "\u8003\u8005\u8010\u8015\u8017\u8033\u8056\u805E\u8074\u8077"
+                + "\u8089\u808C\u8096\u809D\u80A2\u80A5\u80A9\u80AA\u80AF\u80B2"
+                + "\u80BA\u80C3\u80C6\u80CC\u80CE\u80DE\u80F4\u80F8\u80FD\u8102"
+                + "\u8105\u8108\u811A\u8131\u8133\u8139\u8150\u8155\u8170\u8178"
+                + "\u8179\u819A\u819C\u81A8\u81D3\u81E3\u81E8\u81EA\u81ED\u81F3"
+                + "\u81F4\u8208\u820C\u820E\u8217\u821E\u821F\u822A\u822C\u8236"
+                + "\u8239\u8247\u8266\u826F\u8272\u828B\u829D\u82B1\u82B3\u82B8"
+                + "\u82BD\u82D7\u82E5\u82E6\u82F1\u8302\u830E\u8336\u8349\u8352"
+                + "\u8358\u8377\u83CA\u83CC\u83D3\u83DC\u83EF\u843D\u8449\u8457"
+                + "\u846C\u84B8\u84C4\u8535\u8584\u85A6\u85AA-\u85AC\u85E4\u85E9"
+                + "\u85FB\u864E\u8650\u865A\u865C\u865E\u866B\u868A\u8695\u86C7"
+                + "\u86CD\u86EE\u878D\u8840\u8846\u884C\u8853\u8857\u885B\u885D"
+                + "\u8861\u8863\u8868\u8870\u8877\u888B\u88AB\u88C1\u88C2\u88C5"
+                + "\u88CF\u88D5\u88DC\u88F8\u88FD\u8907\u8910\u8912\u895F\u8972"
+                + "\u897F\u8981\u8986\u8987\u898B\u898F\u8996\u899A\u89A7\u89AA"
+                + "\u89B3\u89D2\u89E3\u89E6\u8A00\u8A02\u8A08\u8A0E\u8A13\u8A17"
+                + "\u8A18\u8A1F\u8A2A\u8A2D\u8A31\u8A33\u8A34\u8A3A\u8A3C\u8A50"
+                + "\u8A54\u8A55\u8A5E\u8A60\u8A66\u8A69\u8A70-\u8A73\u8A87\u8A89"
+                + "\u8A8C\u8A8D\u8A93\u8A95\u8A98\u8A9E\u8AA0\u8AA4\u8AAC\u8AAD"
+                + "\u8AB0\u8AB2\u8ABF\u8AC7\u8ACB\u8AD6\u8AED\u8AEE\u8AF8\u8AFE"
+                + "\u8B00\u8B01\u8B04\u8B19\u8B1B\u8B1D\u8B21\u8B39\u8B58\u8B5C"
+                + "\u8B66\u8B70\u8B72\u8B77\u8C37\u8C46\u8C4A\u8C5A\u8C61\u8C6A"
+                + "\u8C9D\u8C9E\u8CA0-\u8CA2\u8CA7-\u8CA9\u8CAB\u8CAC\u8CAF\u8CB4"
+                + "\u8CB7\u8CB8\u8CBB\u8CBF\u8CC0\u8CC3\u8CC4\u8CC7\u8CCA\u8CD3"
+                + "\u8CDB\u8CDC\u8CDE\u8CE0\u8CE2\u8CE6\u8CEA\u8CFC\u8D08\u8D64"
+                + "\u8D66\u8D70\u8D74\u8D77\u8D85\u8D8A\u8DA3\u8DB3\u8DDD\u8DE1"
+                + "\u8DEF\u8DF3\u8DF5\u8E0A\u8E0F\u8E8D\u8EAB\u8ECA\u8ECC\u8ECD"
+                + "\u8ED2\u8EDF\u8EE2\u8EF8\u8EFD\u8F03\u8F09\u8F1D\u8F29\u8F2A"
+                + "\u8F38\u8F44\u8F9B\u8F9E\u8FB0-\u8FB2\u8FBA\u8FBC\u8FC5\u8FCE"
+                + "\u8FD1\u8FD4\u8FEB\u8FED\u8FF0\u8FF7\u8FFD\u9000\u9001\u9003"
+                + "\u9006\u900F\u9010\u9013\u9014\u901A\u901D\u901F\u9020\u9023"
+                + "\u902E\u9031\u9032\u9038\u9042\u9045\u9047\u904A\u904B\u904D"
+                + "\u904E\u9053-\u9055\u9060\u9063\u9069\u906D\u906E\u9075\u9077\u9078"
+                + "\u907A\u907F\u9084\u90A6\u90AA\u90B8\u90CA\u90CE\u90E1\u90E8"
+                + "\u90ED\u90F5\u90F7\u90FD\u9149\u914C\u914D\u9152\u9154\u9162"
+                + "\u916A\u916C\u9175\u9177\u9178\u919C\u91B8\u91C8\u91CC-\u91CF"
+                + "\u91D1\u91DD\u91E3\u920D\u9234\u9244\u925B\u9262\u9271\u9280"
+                + "\u9283\u9285\u9291\u9298\u92AD\u92ED\u92F3\u92FC\u9304\u9318"
+                + "\u9320\u932C\u932F\u9332\u935B\u9396\u93AE\u93E1\u9418\u9451"
+                + "\u9577\u9580\u9589\u958B\u958F\u9591\u9593\u95A2\u95A3\u95A5"
+                + "\u95B2\u95D8\u962A\u9632\u963B\u9644\u964D\u9650\u965B\u9662-\u9665"
+                + "\u966A\u9670\u9673\u9675\u9676\u9678\u967A\u967D\u9685\u9686"
+                + "\u968A\u968E\u968F\u9694\u969B\u969C\u96A0\u96A3\u96B7\u96BB"
+                + "\u96C4-\u96C7\u96C9\u96CC\u96D1\u96E2\u96E3\u96E8\u96EA\u96F0"
+                + "\u96F2\u96F6\u96F7\u96FB\u9700\u9707\u970A\u971C\u9727\u9732"
+                + "\u9752\u9759\u975E\u9762\u9769\u9774\u97D3\u97F3\u97FB\u97FF"
+                + "\u9802\u9803\u9805\u9806\u9810-\u9812\u9818\u982D\u983B\u983C"
+                + "\u984C\u984D\u9854\u9855\u9858\u985E\u9867\u98A8\u98DB\u98DF"
+                + "\u98E2\u98EF\u98F2\u98FC-\u98FE\u990A\u9913\u9928\u9996\u9999\u99AC"
+                + "\u99C4-\u99C6\u99D0\u9A0E\u9A12\u9A13\u9A30\u9A5A\u9AA8\u9AC4"
+                + "\u9AD8\u9AEA\u9B3C\u9B42\u9B45\u9B54\u9B5A\u9BAE\u9BE8\u9CE5"
+                + "\u9CEF\u9CF4\u9D8F\u9E7F\u9E97\u9EA6\u9EBB\u9EC4\u9ED2\u9ED9"
+                + "\u9F13\u9F20\u9F3B\u9F62\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F"
+                + "\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF61-\uFF65]"));
+        EXEMPLAR_MAP.put("ka", new UnicodeSet("[\u10D0-\u10FB\u2116\u2D00-\u2D25]"));
+        EXEMPLAR_MAP.put("kea", new UnicodeSet("[{n\u0308}]"));
+        EXEMPLAR_MAP.put("kkj", new UnicodeSet("[\u2039\u203A{I\u0327}{U\u0327}{a\u0327}{i\u0327}"
+                + "{u\u0327}{\u0186\u0327}{\u0254\u0300}{\u0254\u0302}{\u0254\u0327}{\u025B\u0300}"
+                + "{\u025B\u0302}{\u025B\u0327}]"));
+        EXEMPLAR_MAP.put("km", new UnicodeSet("[\u1780-\u17A2\u17A5-\u17A7\u17A9-\u17B3"
+                + "\u17B6-\u17D2\u17D4-\u17D6\u17D9\u17DA\u200B{\u17A2\u17B6}{\u17A7\u1780}]"));
+        EXEMPLAR_MAP.put("kn", new UnicodeSet("[\u0C82\u0C83\u0C85-\u0C8C\u0C8E-\u0C90"
+                + "\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD"
+                + "\u0CD5\u0CD6\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF]"));
+        EXEMPLAR_MAP.put("ko", new UnicodeSet("[\u1100-\u1112\u1161-\u1175\u11A8-\u11C2\u2015"
+                + "\u2025\u2030\u203B\u203E\u3001-\u3003\u3008-\u3011\u3014\u3015\u301C\u30FB\u3131"
+                + "\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A-\u314E\u4E18\u4E32"
+                + "\u4E43\u4E45\u4E56\u4E5D\u4E5E\u4E6B\u4E7E\u4E82\u4E98\u4EA4"
+                + "\u4EAC\u4EC7\u4ECA\u4ECB\u4EF6\u4EF7\u4F01\u4F0B\u4F0E\u4F3D"
+                + "\u4F73\u4F76\u4F83\u4F86\u4F8A\u4F9B\u4FC2\u4FD3\u4FF1\u500B"
+                + "\u501E\u5026\u5028\u5047\u5048\u5065\u5080\u5091\u50BE\u50C5"
+                + "\u50D1\u50F9\u5106\u5109\u513A\u5149\u514B\u5162\u5167\u516C"
+                + "\u5171\u5176\u5177\u517C\u5180\u51A0\u51F1\u520A\u522E\u5238"
+                + "\u523B\u524B\u525B\u5287\u528D\u5292\u529F\u52A0\u52A4\u52AB"
+                + "\u52C1\u52CD\u52D8\u52E4\u52F8\u52FB\u52FE\u5321\u5323\u5340"
+                + "\u5357\u5366\u5374\u5375\u5377\u537F\u53A5\u53BB\u53CA\u53E3"
+                + "\u53E5\u53E9\u53EB\u53EF\u5404\u5409\u541B\u544A\u5471\u5475"
+                + "\u548E\u54AC\u54E5\u54ED\u5553\u5580\u5587\u559D\u55AB\u55AC"
+                + "\u55DC\u5609\u5614\u5668\u56CA\u56F0\u56FA\u5708\u570B\u572D"
+                + "\u573B\u5747\u574E\u5751\u5764\u5770\u5775\u57A2\u57FA\u57FC"
+                + "\u5800\u5805\u5808\u582A\u583A\u584A\u584F\u5883\u58BE\u58D9"
+                + "\u58DE\u5914\u5947\u5948\u594E\u5951\u5978\u5993\u5997\u59D1"
+                + "\u59DC\u59E6\u5A18\u5A1C\u5AC1\u5B0C\u5B54\u5B63\u5B64\u5B8F"
+                + "\u5B98\u5BA2\u5BAE\u5BB6\u5BC4\u5BC7\u5BE1\u5BEC\u5C3B\u5C40"
+                + "\u5C45\u5C46\u5C48\u5C90\u5CA1\u5CAC\u5D0E\u5D11\u5D17\u5D4C"
+                + "\u5D50\u5D87\u5DA0\u5DE5\u5DE7\u5DE8\u5DF1\u5DFE\u5E72\u5E79"
+                + "\u5E7E\u5E9A\u5EAB\u5EB7\u5ECA\u5ED0\u5ED3\u5EE3\u5EFA\u5F13"
+                + "\u5F3A\u5F4A\u5F91\u5FCC\u6025\u602A\u602F\u6050\u605D\u606A"
+                + "\u606D\u60B8\u6106\u611F\u6127\u6137\u613E\u614A\u6163\u6164"
+                + "\u6168\u6176\u6177\u61A9\u61AC\u61BE\u61C3\u61C7\u61E6\u61F6"
+                + "\u61FC\u6208\u6212\u621F\u6221\u6271\u6280\u6289\u62C9\u62CF"
+                + "\u62D0\u62D2\u62D8\u62EC\u62EE\u62F1\u62F3\u62F7\u62FF\u634F"
+                + "\u636E\u6372\u637A\u6398\u639B\u63A7\u63C0\u63C6\u63ED\u64CA"
+                + "\u64CE\u64D2\u64DA\u64E7\u652A\u6537\u6539\u653B\u6545\u654E"
+                + "\u6551\u6562\u656C\u6572\u659B\u65A4\u65D7\u65E3\u6606\u6611"
+                + "\u666F\u6677\u6687\u6696\u66A0\u66BB\u66E0\u66F2\u66F4\u66F7"
+                + "\u6717\u671E\u671F\u673A\u6746\u675E\u6770\u678F\u679C\u67AF"
+                + "\u67B6\u67B8\u67D1\u67E9\u67EC\u67EF\u6821\u6839\u683C\u6840"
+                + "\u6842\u6854\u687F\u688F\u6897\u68B0\u68B1\u68C4\u68CB\u68CD"
+                + "\u68D8\u68E8\u68FA\u6957\u6960\u6975\u69C1\u69CB\u69D0\u69E8"
+                + "\u69EA\u69FB\u69FF\u6A02\u6A44\u6A4B\u6A58\u6A5F\u6A84\u6A8E"
+                + "\u6AA2\u6AC3\u6B04\u6B0A\u6B3A\u6B3E\u6B4C\u6B50\u6B78\u6BBC"
+                + "\u6BC6\u6BEC\u6C23\u6C42\u6C5F\u6C68\u6C72\u6C7A\u6C7D\u6C82"
+                + "\u6CBD\u6D1B\u6D38\u6D6A\u6D87\u6DC3\u6DC7\u6E1B\u6E20\u6E34"
+                + "\u6E73\u6E9D\u6EAA\u6ED1\u6EFE\u6F11\u6F54\u6F70\u6F97\u6FC0"
+                + "\u6FEB\u704C\u7078\u7085\u709A\u70AC\u70D9\u70F1\u7156\u721B"
+                + "\u727D\u72AC\u72C2\u72D7\u72E1\u72FC\u7357\u7396\u7398\u73C2"
+                + "\u73CF\u73D6\u73D9\u73DE\u73EA\u7403\u7426\u7428\u742A\u742F"
+                + "\u7434\u747E\u7482\u749F\u74A3\u74A5\u74CA\u74D8\u74DC\u7504"
+                + "\u7518\u7532\u7537\u7547\u754C\u7578\u757A\u757F\u7586\u75A5"
+                + "\u75B3\u75C2\u75D9\u75FC\u764E\u7669\u7678\u7686\u768E\u7690"
+                + "\u76D6\u76E3\u770B\u7737\u777E\u77B0\u77BC\u77BF\u77DC\u77E9"
+                + "\u77EF\u7845\u786C\u7881\u78A3\u78CE\u78EC\u78EF\u78F5\u7941"
+                + "\u7947\u7948\u795B\u797A\u7981\u79BD\u79D1\u7A08\u7A3C\u7A3D"
+                + "\u7A3F\u7A40\u7A76\u7A79\u7A7A\u7A98\u7A9F\u7AAE\u7ABA\u7AC5"
+                + "\u7ADF\u7AED\u7AF6\u7AFF\u7B4B\u7B50\u7B60\u7B87\u7B95\u7B9D"
+                + "\u7BA1\u7C21\u7CB3\u7CE0\u7CFB\u7CFE\u7D00\u7D0D\u7D18\u7D1A"
+                + "\u7D3A\u7D45\u7D50\u7D5E\u7D66\u7D73\u7D79\u7D7F\u7D93\u7DB1"
+                + "\u7DBA\u7DCA\u7E6B\u7E6D\u7E7C\u7F3A\u7F50\u7F6B\u7F85\u7F88"
+                + "\u7F8C\u7F94\u7FA4\u7FB9\u7FF9\u8003\u8006\u8009\u8015\u802D"
+                + "\u803F\u808C\u809D\u80A1\u80A9\u80AF\u80B1\u80DB\u80F1\u811A"
+                + "\u811B\u8154\u8171\u8188\u818F\u81A0\u81D8\u81FC\u8205\u820A"
+                + "\u8221\u826E\u8271\u828E\u82A5\u82A9\u82B9\u82DB\u82DF\u82E6"
+                + "\u82FD\u8304\u8396\u83C5\u83CA\u83CC\u83D3\u83EB\u83F0\u843D"
+                + "\u845B\u8475\u84CB\u854E\u8568\u8591\u85C1\u85CD\u85FF\u862D"
+                + "\u863F\u8654\u86A3\u86DF\u874E\u87BA\u881F\u8831\u8857\u8862"
+                + "\u8872\u887E\u887F\u8888\u889E\u88B4\u88D9\u88F8\u8910\u8941"
+                + "\u895F\u8964\u898B\u898F\u89A1\u89B2\u89BA\u89C0\u89D2\u8A08"
+                + "\u8A18\u8A23\u8A36\u8A6D\u8A87\u8AA1\u8AA5\u8AB2\u8AEB\u8AFE"
+                + "\u8B19\u8B1B\u8B33\u8B39\u8B4F\u8B66\u8B74\u8C37\u8C3F\u8C48"
+                + "\u8CA2\u8CAB\u8CB4\u8CC8\u8CFC\u8D73\u8D77\u8DCF\u8DDD\u8DE8"
+                + "\u8E1E\u8E47\u8E76\u8EAC\u8EC0\u8ECA\u8ECC\u8ECD\u8EFB\u8F03"
+                + "\u8F15\u8F4E\u8F5F\u8F9C\u8FD1\u8FE6\u8FF2\u9002\u9011\u9015"
+                + "\u9035\u904E\u9063\u907D\u908F\u90A3\u90AF\u90B1\u90CA\u90CE"
+                + "\u90E1\u90ED\u916A\u91B5\u91D1\u9210\u921E\u9240\u9245\u9257"
+                + "\u9264\u92B6\u92F8\u92FC\u9321\u9324\u9326\u932E\u934B\u9375"
+                + "\u938C\u93A7\u93E1\u9451\u9452\u945B\u958B\u9593\u9598\u95A3"
+                + "\u95A8\u95D5\u95DC\u964D\u968E\u9694\u9699\u96C7\u96E3\u978F"
+                + "\u97A0\u97A8\u97AB\u9803\u9838\u9846\u9867\u98E2\u9903\u9928"
+                + "\u9949\u994B\u9951\u99D2\u99D5\u99F1\u9A0E\u9A0F\u9A2B\u9A45"
+                + "\u9A55\u9A5A\u9A65\u9AA8\u9AD8\u9B3C\u9B41\u9BAB\u9BE4\u9BE8"
+                + "\u9C47\u9CE9\u9D51\u9D60\u9DC4\u9DD7\u9E1E\u9E92\u9EB4\u9ED4"
+                + "\u9F13\u9F95\u9F9C\uAC00-\uD7A3\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F"
+                + "\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D]"));
+        EXEMPLAR_MAP.put("kok", new UnicodeSet("[\u0901-\u0903\u0905-\u090D\u090F-\u0911"
+                + "\u0913-\u0928\u092A-\u0930\u0932\u0933\u0935-\u0939\u093C-\u0945\u0947-\u0949"
+                + "\u094B-\u094D\u0950\u0966-\u096F{\u0915\u093C}{\u0916\u093C}"
+                + "{\u0917\u093C}{\u091C\u093C}{\u0921\u093C}{\u0922\u093C}{\u092B\u093C}"
+                + "{\u092F\u093C}]"));
+        EXEMPLAR_MAP.put("ksh", new UnicodeSet("[\u2E17]"));
+        EXEMPLAR_MAP.put("lkt", new UnicodeSet("[\u02BC{k\u02BC}{p\u02BC}{s\u02BC}{t\u02BC}"
+                + "{\u010D\u02BC}{\u0161\u02BC}{\u021F\u02BC}]"));
+        EXEMPLAR_MAP.put("ln", new UnicodeSet("[{\u0254\u0302}{\u0254\u030C}{\u025B\u0302}"
+                + "{\u025B\u030C}]"));
+        EXEMPLAR_MAP.put("lo", new UnicodeSet("[\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D"
+                + "\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9"
+                + "\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC\u0EDD\u200B"
+                + "{\u0EAB\u0E87}{\u0EAB\u0E8D}{\u0EAB\u0E99}{\u0EAB\u0EA1}{\u0EAB\u0EA5}"
+                + "{\u0EAB\u0EA7}]"));
+        EXEMPLAR_MAP.put("lt", new UnicodeSet("[{i\u0307\u0300}{i\u0307\u0301}"
+                + "{i\u0307\u0303}{j\u0303}{j\u0307\u0303}{l\u0303}{m\u0303}{r\u0303}{\u0105\u0303}"
+                + "{\u0117\u0303}{\u0119\u0303}{\u012F\u0303}{\u012F\u0307\u0301}"
+                + "{\u012F\u0307\u0303}{\u016B\u0303}{\u0173\u0303}]"));
+        EXEMPLAR_MAP.put("lu", new UnicodeSet("[{\u0254\u0300}{\u025B\u0300}]"));
+        EXEMPLAR_MAP.put("mgo", new UnicodeSet("[\u02BC{\u0254\u0300}{\u0259\u0300}]"));
+        EXEMPLAR_MAP.put("ml", new UnicodeSet("[\u0D02\u0D03\u0D05-\u0D0C\u0D0E-\u0D10"
+                + "\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D43\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D60"
+                + "\u0D61\u0D7A-\u0D7F]"));
+        EXEMPLAR_MAP.put("mr", new UnicodeSet("[\u0901-\u0903\u0905-\u090D\u090F-\u0911"
+                + "\u0913-\u0928\u092A-\u0930\u0932\u0933\u0935-\u0939\u093C-\u0945\u0947-\u0949"
+                + "\u094B-\u094D\u0950\u0966-\u096F]"));
+        EXEMPLAR_MAP.put("my", new UnicodeSet("[\u1000-\u1032\u1036-\u104B\u1050-\u1059]"));
+        EXEMPLAR_MAP.put("mzn", new UnicodeSet("[\u060C\u061B\u061F\u0621-\u063A\u0641-\u0652"
+                + "\u0654\u0656\u066A-\u066C\u0670\u067E\u0686\u0698\u06A9\u06AF\u06CC\u06F0-\u06F9"
+                + "\u2039\u203A]"));
+        EXEMPLAR_MAP.put("ne", new UnicodeSet("[\u0901-\u0903\u0905-\u090D\u090F-\u0911"
+                + "\u0913-\u0928\u092A-\u0930\u0932\u0933\u0935-\u0939\u093C-\u0945\u0947-\u0949"
+                + "\u094B-\u094D\u0950\u0966-\u096F]"));
+        EXEMPLAR_MAP.put("nmg", new UnicodeSet("[{\u01DD\u0302}{\u01DD\u0304}{\u01DD\u030C}"
+                + "{\u0254\u0302}{\u0254\u0304}{\u0254\u030C}{\u025B\u0302}{\u025B\u0304}"
+                + "{\u025B\u030C}]"));
+        EXEMPLAR_MAP.put("nnh", new UnicodeSet("[\u02BC{\u0254\u0300}{\u0254\u0302}{\u0254\u030C}"
+                + "{\u025B\u0300}{\u025B\u0302}{\u025B\u030C}{\u0289\u0300}{\u0289\u0302}"
+                + "{\u0289\u030C}]"));
+        EXEMPLAR_MAP.put("nus", new UnicodeSet("[{a\u0331}{e\u0331}{i\u0331}{o\u0331}"
+                + "{\u0254\u0308}{\u0254\u0331}{\u025B\u0308}{\u025B\u0331}{\u025B\u0331\u0308}]"));
+        EXEMPLAR_MAP.put("or", new UnicodeSet("[\u0B01-\u0B03\u0B05-\u0B0B\u0B0F\u0B10"
+                + "\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C\u0B3E-\u0B43\u0B47"
+                + "\u0B48\u0B4B-\u0B4D\u0B5F\u0B71{\u0B15\u0B4D\u0B37}{\u0B21\u0B3C}"
+                + "{\u0B22\u0B3C}]"));
+        EXEMPLAR_MAP.put("pa-Arab", new UnicodeSet("[\u0621-\u0624\u0626-\u063A\u0641\u0642"
+                + "\u0644-\u0648\u064F\u066A-\u066C\u0679-\u067E\u0686\u0688\u0691\u0698\u06A9"
+                + "\u06AF\u06BA\u06BE\u06C1\u06CC\u06D2\u06F0-\u06F9]"));
+        EXEMPLAR_MAP.put("pa-Guru", new UnicodeSet("[\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10"
+                + "\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A35\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47"
+                + "\u0A48\u0A4B-\u0A4D\u0A5C\u0A66-\u0A74{\u0A16\u0A3C}{\u0A17\u0A3C}"
+                + "{\u0A1C\u0A3C}{\u0A2B\u0A3C}{\u0A32\u0A3C}{\u0A38\u0A3C}]"));
+        EXEMPLAR_MAP.put("ps", new UnicodeSet("[\u0621-\u0624\u0626-\u063A\u0641\u0642"
+                + "\u0644-\u0648\u064A-\u0652\u0654\u066A-\u066C\u0670\u067C\u067E\u0681\u0685"
+                + "\u0686\u0689\u0693\u0696\u0698\u069A\u06A9\u06AB\u06AF\u06BC\u06CC\u06CD\u06D0"
+                + "\u06F0-\u06F9]"));
+        EXEMPLAR_MAP.put("qu", new UnicodeSet("[{ch\u02BC}{k\u02BC}{p\u02BC}{q\u02BC}{t\u02BC}]"));
+        EXEMPLAR_MAP.put("si", new UnicodeSet("[\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1"
+                + "\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2"
+                + "\u0DF3\u200B-\u200D]"));
+        EXEMPLAR_MAP.put("ta", new UnicodeSet("[\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95"
+                + "\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9"
+                + "\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD{\u0B95\u0BCD\u0BB7}]"));
+        EXEMPLAR_MAP.put("te", new UnicodeSet("[\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10"
+                + "\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D"
+                + "\u0C55\u0C56\u0C60\u0C61\u0C66-\u0C6F]"));
+        EXEMPLAR_MAP.put("th", new UnicodeSet("[\u0E01-\u0E3A\u0E40-\u0E4E\u200B]"));
+        EXEMPLAR_MAP.put("ti", new UnicodeSet("[\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258"
+                + "\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE"
+                + "\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135F"
+                + "\u1380-\u1399\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE"
+                + "\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE]"));
+        EXEMPLAR_MAP.put("to", new UnicodeSet("[\u02BB]"));
+        EXEMPLAR_MAP.put("ug", new UnicodeSet("[\u0626-\u0628\u062A\u062C\u062E\u062F"
+                + "\u0631-\u0634\u063A\u0641-\u0646\u0648-\u064A\u067E\u0686\u0698\u06AD\u06AF"
+                + "\u06BE\u06C6-\u06C8\u06CB\u06D0\u06D5]"));
+        EXEMPLAR_MAP.put("uk", new UnicodeSet("[\u02BC\u2116]"));
+        EXEMPLAR_MAP.put("ur-IN", new UnicodeSet("[\u0600-\u0603\u060C\u060D\u061B\u061F"
+                + "\u0621-\u0624\u0626-\u063A\u0641\u0642\u0644-\u0648\u064A-\u0652\u0654"
+                + "\u0656-\u0658\u066B\u066C\u0670\u0679-\u067E\u0686\u0688\u0691\u0698\u06A9\u06AF"
+                + "\u06BA\u06BE\u06C1-\u06C3\u06CC\u06D2\u06D4\u06F0-\u06F9]"));
+        EXEMPLAR_MAP.put("ur-PK", new UnicodeSet("[\u0600-\u0603\u060C\u060D\u061B\u061F"
+                + "\u0621-\u0624\u0626-\u063A\u0641\u0642\u0644-\u0648\u064A-\u0652\u0654"
+                + "\u0656-\u0658\u066B\u066C\u0670\u0679-\u067E\u0686\u0688\u0691\u0698\u06A9\u06AF"
+                + "\u06BA\u06BE\u06C1-\u06C3\u06CC\u06D2\u06D4]"));
+        EXEMPLAR_MAP.put("uz-Arab", new UnicodeSet("[\u0621-\u0624\u0626-\u063A\u0641\u0642"
+                + "\u0644-\u0648\u064A-\u0652\u0654\u066A-\u066C\u0670\u067C\u067E\u0681\u0685"
+                + "\u0686\u0689\u0693\u0696\u0698\u069A\u06A9\u06AB\u06AF\u06BC\u06C7\u06C9\u06CC"
+                + "\u06CD\u06D0\u06F0-\u06F9]"));
+        EXEMPLAR_MAP.put("uz-Latn", new UnicodeSet("[\u02BC{G\u02BB}{O\u02BB}{g\u02BB}{o\u02BB}]"));
+        EXEMPLAR_MAP.put("yav", new UnicodeSet("[{\u0254\u0300}{\u025B\u0300}]"));
+        EXEMPLAR_MAP.put("yue", new UnicodeSet("[\u2025\u2027\u2030\u2035\u203B\u203E"
+                + "\u3001-\u3003\u3008-\u3011\u3014\u3015\u301D\u301E\u4E00\u4E01\u4E03"
+                + "\u4E08-\u4E0D\u4E11\u4E14\u4E16\u4E18\u4E19\u4E1E\u4E1F\u4E26\u4E2D\u4E32\u4E38"
+                + "\u4E39\u4E3B\u4E43\u4E45\u4E48\u4E4B\u4E4D-\u4E4F\u4E56\u4E58\u4E59\u4E5D"
+                + "\u4E5F\u4E7E\u4E82\u4E86\u4E88\u4E8B\u4E8C\u4E8E\u4E91\u4E92"
+                + "\u4E94\u4E95\u4E9B\u4E9E\u4EA1\u4EA4-\u4EA6\u4EA8\u4EAB-\u4EAE"
+                + "\u4EBA\u4EC0-\u4EC2\u4EC7\u4ECA\u4ECB\u4ECD\u4ED4\u4ED6\u4ED8\u4ED9"
+                + "\u4EE3-\u4EE5\u4EF0\u4EF2\u4EF6\u4EFB\u4EFD\u4F01\u4F0A\u4F0D"
+                + "\u4F0F-\u4F11\u4F19\u4F2F\u4F30\u4F34\u4F38\u4F3C\u4F3D\u4F46"
+                + "\u4F48\u4F49\u4F4D-\u4F50\u4F54\u4F55\u4F59\u4F5B\u4F5C\u4F60"
+                + "\u4F69\u4F73\u4F7F\u4F86\u4F8B\u4F9B\u4F9D\u4FAF\u4FB5\u4FB6"
+                + "\u4FBF\u4FC2-\u4FC4\u4FCA\u4FD7\u4FDD\u4FE0\u4FE1\u4FEE\u4FF1\u4FFE"
+                + "\u500B\u500D\u5011\u5012\u5019\u501A\u501F\u502B\u503C\u5047"
+                + "\u5049\u504F\u505A\u505C\u5065\u5074-\u5077\u5080\u5091\u5099\u50A2"
+                + "\u50A3\u50B2\u50B3\u50B7\u50BB\u50BE\u50C5\u50CE\u50CF\u50D1"
+                + "\u50E7\u50F3\u50F5\u50F9\u5100\u5104\u5110\u5112\u5118\u511F"
+                + "\u512A\u5133\u5137\u513B\u5141\u5143-\u5149\u514B-\u514D\u5152\u5154"
+                + "\u5165\u5167-\u5169\u516B-\u516E\u5171\u5175-\u5179\u517C\u518A"
+                + "\u518D\u5192\u51A0\u51AC\u51B0\u51B7\u51C6\u51CC\u51DD\u51E1"
+                + "\u51F0\u51F1\u51FA\u51FD\u5200\u5206\u5207\u520A\u5217\u521D"
+                + "\u5224\u5225\u5229-\u522B\u5230\u5236-\u5238\u523A\u523B\u5247\u524C"
+                + "\u524D\u525B\u5269\u526A\u526F\u5272\u5275\u5283\u5287\u5289"
+                + "\u528D\u529B\u529F\u52A0\u52A9-\u52AB\u52C1\u52C7\u52C9\u52D2"
+                + "\u52D5\u52D9\u52DD\u52DE\u52E2\u52E4\u52F3\u52F5\u52F8\u52FF"
+                + "\u5305\u5308\u5316\u5317\u5339\u5340\u5341\u5343\u5347\u5348"
+                + "\u534A\u5351-\u5354\u5357\u535A\u535C\u535E\u5360\u5361\u536F-\u5371"
+                + "\u5373\u5377\u537B\u5384\u5398\u539A\u539F\u53AD\u53B2\u53BB"
+                + "\u53C3\u53C8\u53CA\u53CB\u53CD\u53D4\u53D6\u53D7\u53E2-\u53E6"
+                + "\u53EA-\u53ED\u53EF\u53F0\u53F2\u53F3\u53F6\u53F8\u5403\u5404"
+                + "\u5408-\u540A\u540C-\u540E\u5410-\u5412\u541B\u541D-\u5420\u5426\u5427"
+                + "\u542B\u5433\u5435\u5438\u5439\u543E\u5440\u5442\u5446\u544A"
+                + "\u5462\u5468\u5473\u5475\u547C\u547D\u548C\u5496\u54A6\u54A7"
+                + "\u54AA\u54AC\u54B1\u54C0\u54C1\u54C7-\u54C9\u54CE\u54E1\u54E5\u54E6"
+                + "\u54E9\u54EA\u54ED\u54F2\u5509\u5510\u5514\u552C\u552E\u552F"
+                + "\u5531\u5537\u5538\u5546\u554A\u554F\u555F\u5561\u5565\u5566"
+                + "\u556A\u5580\u5582\u5584\u5587\u558A\u5594\u559C\u559D\u55AC"
+                + "\u55AE\u55B5\u55CE\u55DA\u55E8\u55EF\u5606\u5609\u5617\u561B"
+                + "\u5634\u563B\u563F\u5668\u5674\u5687\u56B4\u56C9\u56CC\u56D1"
+                + "\u56DB\u56DE\u56E0\u56F0\u56FA\u5708\u570B\u570D\u5712\u5713"
+                + "\u5716\u5718\u571C\u571F\u5728\u572D\u5730\u573E\u5740\u5747"
+                + "\u574E\u5750\u5761\u5764\u5766\u576A\u5782\u5783\u578B\u57C3"
+                + "\u57CE\u57D4\u57DF\u57F7\u57F9\u57FA\u5802\u5805\u5806\u5821"
+                + "\u5824\u582A\u5831\u5834\u584A\u5854\u5857\u585E\u586B\u5875"
+                + "\u5883\u588E\u589E\u58A8\u58AE\u58C1\u58C7\u58D3\u58D8\u58DE"
+                + "\u58E2\u58E4\u58EB\u58EC\u58EF\u58FD\u590F\u5915\u5916\u591A"
+                + "\u591C\u5920\u5922\u5925\u5927\u5929-\u592B\u592E\u5931\u5937\u5938"
+                + "\u593E\u5947-\u5949\u594E\u594F\u5951\u5954\u5957\u5965\u5967\u596A"
+                + "\u596E\u5973\u5974\u5976\u5979\u597D\u5982\u5999\u599D\u59A5"
+                + "\u59A8\u59AE\u59B3\u59B9\u59BB\u59C6\u59CA\u59CB\u59D0\u59D1"
+                + "\u59D3\u59D4\u59FF\u5A01\u5A03\u5A18\u5A1B\u5A41\u5A46\u5A5A"
+                + "\u5A66\u5A92\u5ABD\u5ACC\u5AE9\u5B50\u5B54\u5B57\u5B58\u5B5C"
+                + "\u5B5D\u5B5F\u5B63\u5B64\u5B69\u5B6B\u5B78\u5B83\u5B85\u5B87-\u5B89"
+                + "\u5B8B\u5B8C\u5B8F\u5B97-\u5B9C\u5BA2-\u5BA4\u5BAE\u5BB3\u5BB6\u5BB9"
+                + "\u5BBF\u5BC2\u5BC4-\u5BC6\u5BCC\u5BD2\u5BDE\u5BDF\u5BE2\u5BE6-\u5BE9"
+                + "\u5BEB\u5BEC\u5BEE\u5BF5\u5BF6\u5C01\u5C04\u5C07\u5C08\u5C0A"
+                + "\u5C0B\u5C0D-\u5C0F\u5C11\u5C16\u5C1A\u5C24\u5C31\u5C3A\u5C3C\u5C3E"
+                + "\u5C40\u5C41\u5C45\u5C46\u5C4B\u5C4F\u5C55\u5C60\u5C64\u5C6C"
+                + "\u5C71\u5CA1\u5CA9\u5CB8\u5CC7\u5CF0\u5CF6\u5CFD\u5D07\u5D19"
+                + "\u5D34\u5D50\u5DBA\u5DBC\u5DDD\u5DDE\u5DE1\u5DE5-\u5DE8\u5DEB\u5DEE"
+                + "\u5DF1-\u5DF4\u5DF7\u5DFD\u5E02\u5E03\u5E0C\u5E15\u5E16\u5E1B"
+                + "\u5E1D\u5E25\u5E2B\u5E2D\u5E33\u5E36\u5E38\u5E3D\u5E45\u5E55"
+                + "\u5E63\u5E6B\u5E72-\u5E74\u5E78\u5E79\u5E7B-\u5E7E\u5E87\u5E8A"
+                + "\u5E8F\u5E95\u5E97\u5E9A\u5E9C\u5EA6\u5EA7\u5EAB\u5EAD\u5EB7"
+                + "\u5EB8\u5EC9\u5ED6\u5EE0\u5EE2\u5EE3\u5EF3\u5EF6\u5EF7\u5EFA"
+                + "\u5F04\u5F0F\u5F15\u5F17\u5F18\u5F1F\u5F26\u5F31\u5F35\u5F37"
+                + "\u5F48\u5F4A\u5F4C\u5F4E\u5F5D\u5F5E\u5F62\u5F65\u5F69\u5F6C"
+                + "\u5F6D\u5F70\u5F71\u5F79\u5F7C\u5F80\u5F81\u5F85\u5F88\u5F8B"
+                + "\u5F8C\u5F90-\u5F92\u5F97\u5F9E\u5FA9\u5FAE\u5FB5\u5FB7\u5FB9\u5FC3"
+                + "\u5FC5\u5FCC\u5FCD\u5FD7-\u5FD9\u5FE0\u5FEB\u5FF5\u5FFD\u600E\u6012"
+                + "\u6015\u6016\u601D\u6021\u6025\u6027\u6028\u602A\u6046\u6050"
+                + "\u6062\u6065\u6068\u6069\u606D\u606F\u6070\u6085\u6089\u6094"
+                + "\u609F\u60A0\u60A8\u60B2\u60B6\u60C5\u60D1\u60DC\u60E0\u60E1"
+                + "\u60F1\u60F3\u60F9\u6101\u6108\u6109\u610F\u611A\u611B\u611F"
+                + "\u6148\u614B\u6155\u6158\u6162\u6163\u6167\u616E\u6170\u6176"
+                + "\u617E\u6182\u6190\u6191\u61B2\u61B6\u61BE\u61C2\u61C9\u61F6"
+                + "\u61F7\u61FC\u6200\u6208\u620A\u620C\u6210-\u6212\u6216\u622A"
+                + "\u6230\u6232\u6234\u6236\u623F-\u6241\u6247\u624B\u624D\u624E"
+                + "\u6253\u6258\u6263\u6265\u626D\u626F\u6279\u627E-\u6280\u6284\u628A"
+                + "\u6293\u6295\u6297\u6298\u62AB\u62AC\u62B1\u62B5\u62B9\u62BD"
+                + "\u62C6\u62C9\u62CB\u62CD\u62CF\u62D2\u62D4\u62D6\u62DB\u62DC"
+                + "\u62EC\u62F3\u62FC\u62FE\u62FF\u6301\u6307\u6309\u6311\u6316"
+                + "\u632A\u632F\u633A\u6350\u6355\u6368\u6372\u6377\u6383\u6388"
+                + "\u6389\u638C\u6392\u639B\u63A1\u63A2\u63A5\u63A7\u63A8\u63AA"
+                + "\u63CF\u63D0\u63D2\u63DA\u63DB\u63E1\u63EE\u63F4\u640D\u6416"
+                + "\u641C\u641E\u642C\u642D\u6436\u6458\u6469\u6478\u6490\u6492"
+                + "\u649E\u64A3\u64A5\u64AD\u64BE\u64BF\u64C1\u64C7\u64CA\u64CB"
+                + "\u64CD\u64CE\u64D4\u64DA\u64E0\u64E6\u64EC\u64F4\u64FA\u64FE"
+                + "\u651D\u652F\u6536\u6539\u653B\u653E\u653F\u6545\u6548\u654D"
+                + "\u654F\u6551\u6557-\u6559\u655D\u6562\u6563\u6566\u656C\u6574"
+                + "\u6575\u6578\u6587\u6590\u6597\u6599\u65AF\u65B0\u65B7\u65B9"
+                + "\u65BC\u65BD\u65C1\u65C5\u65CB\u65CF\u65D7\u65E2\u65E5\u65E6"
+                + "\u65E9\u65ED\u65FA\u6602\u6606\u6607\u660C\u660E\u660F\u6613"
+                + "\u661F\u6620\u6625\u6628\u662D\u662F\u6642\u6649\u6652\u665A"
+                + "\u6668\u666E\u666F\u6674\u6676\u667A\u6691\u6696\u6697\u66AB"
+                + "\u66B4\u66C6\u66C9\u66F0\u66F2\u66F4\u66F8\u66FC\u66FE-\u6700"
+                + "\u6703\u6708\u6709\u670B\u670D\u6717\u671B\u671D\u671F\u6728"
+                + "\u672A-\u672D\u6731\u6735\u6749\u674E\u6750\u6751\u675C\u675F"
+                + "\u676F-\u6771\u677E\u677F\u6790\u6797\u679C\u679D\u67B6\u67CF"
+                + "\u67D0\u67D3\u67D4\u67E5\u67EC\u67EF\u67F3\u67F4\u6817\u6821"
+                + "\u6838\u6839\u683C\u6843\u6848\u684C\u6851\u6881\u6885\u689D"
+                + "\u68A8\u68AF\u68B0\u68B5\u68C4\u68C9\u68CB\u68D2\u68DA\u68EE"
+                + "\u6905\u690D\u6930\u694A\u6953\u6954\u695A\u696D\u6975\u6982"
+                + "\u699C\u69AE\u69CB\u69CD\u6A02\u6A13\u6A19\u6A1E\u6A21\u6A23"
+                + "\u6A39\u6A4B\u6A5F\u6A6B\u6A80\u6A94\u6AA2\u6B04\u6B0A\u6B21"
+                + "\u6B23\u6B32\u6B3A\u6B3D\u6B3E\u6B49\u6B4C\u6B50\u6B61-\u6B66"
+                + "\u6B72\u6B77\u6B78\u6B7B\u6B8A\u6B98\u6BB5\u6BBA\u6BBC\u6BC0"
+                + "\u6BC5\u6BCD\u6BCF\u6BD2\u6BD4\u6BDB\u6BEB\u6C0F\u6C11\u6C23"
+                + "\u6C34\u6C38\u6C42\u6C57\u6C5D\u6C5F-\u6C61\u6C6A\u6C76\u6C7A\u6C7D"
+                + "\u6C83\u6C88\u6C89\u6C92\u6C96\u6C99\u6CB3\u6CB9\u6CBB\u6CBF"
+                + "\u6CC1\u6CC9\u6CCA\u6CD5\u6CE1\u6CE2\u6CE5\u6CE8\u6CF0\u6CF3"
+                + "\u6D0B\u6D17\u6D1B\u6D1E\u6D29\u6D2A\u6D32\u6D3B\u6D3D\u6D3E"
+                + "\u6D41\u6D66\u6D69\u6D6A\u6D6E\u6D77\u6D85\u6D87-\u6D89\u6DAF\u6DB2"
+                + "\u6DB5\u6DBC\u6DD1\u6DDA\u6DE1\u6DE8\u6DF1\u6DF7\u6DFA\u6E05"
+                + "\u6E1B\u6E21\u6E2C\u6E2F\u6E38\u6E3E\u6E56\u6E6F\u6E90\u6E96"
+                + "\u6E9D\u6EAA\u6EAB\u6EC4\u6EC5\u6ECB\u6ED1\u6EF4\u6EFE\u6EFF"
+                + "\u6F02\u6F0F\u6F14\u6F20\u6F22\u6F2B\u6F32\u6F38\u6F54\u6F58"
+                + "\u6F5B\u6F6E\u6F8E\u6FA4\u6FB3\u6FC0\u6FC3\u6FDF\u6FE4\u6FEB"
+                + "\u6FF1\u700F\u704C\u7063\u706B\u7070\u707D\u708E\u70AE\u70B8"
+                + "\u70BA\u70C8\u70CF\u70E4\u7121\u7126\u7136\u7159\u715E\u7167"
+                + "\u7169\u718A\u719F\u71B1\u71C3\u71C8\u71D2\u71DF\u71E6\u7206"
+                + "\u7210\u721B\u722A\u722C\u722D\u7235\u7236\u7238\u723A\u723D"
+                + "\u723E\u7246-\u7248\u724C\u7259\u725B\u7260\u7267\u7269\u7272\u7279"
+                + "\u727D\u72A7\u72AF\u72C0\u72C2\u72C4\u72D0\u72D7\u72E0\u72FC"
+                + "\u731B\u731C\u7334\u7336\u7344\u7345\u734E\u7368\u7372\u7378"
+                + "\u737B\u7384\u7387\u7389\u738B\u73A9\u73AB\u73B2\u73BB\u73CA"
+                + "\u73CD\u73E0\u73E5\u73ED\u73FE\u7403\u7406\u7409\u742A\u7433"
+                + "\u7434\u7459\u745A\u745C\u745E\u745F\u7464\u746A\u7470\u74B0"
+                + "\u74DC\u74E6\u74F6\u7518\u751A\u751C\u751F\u7522\u7528\u752B"
+                + "\u7530-\u7533\u7537\u7538\u754C\u7559\u7562\u7565\u756A\u756B"
+                + "\u7570\u7576\u7586\u758F\u7591\u75BC\u75C5\u75D5\u75DB\u75F4"
+                + "\u760B\u7642\u7661\u7678\u767B-\u767E\u7684\u7686\u7687\u76AE"
+                + "\u76C3\u76CA\u76DB\u76DC\u76DF\u76E1\u76E3\u76E4\u76E7\u76EE"
+                + "\u76F2\u76F4\u76F8\u76FC\u76FE\u7701\u7709\u770B\u771F\u7720"
+                + "\u773C\u773E\u775B\u7761\u7763\u77A7\u77AD\u77DB\u77E3\u77E5"
+                + "\u77ED\u77F3\u7802\u780D\u7814\u7832\u7834\u786C\u788E\u7891"
+                + "\u7897\u789F\u78A7\u78A9\u78B0\u78BA\u78BC\u78C1\u78E8\u78EF"
+                + "\u7901\u790E\u7919\u793A\u793E\u7955\u7956\u795A\u795B\u795D"
+                + "\u795E\u7965\u7968\u797F\u7981\u798D-\u798F\u79AA\u79AE\u79C0\u79C1"
+                + "\u79CB\u79D1\u79D2\u79D8\u79DF\u79E4\u79E6\u79FB\u7A05\u7A0B"
+                + "\u7A0D\u7A2E\u7A31\u7A3F\u7A46\u7A4C\u7A4D\u7A69\u7A76\u7A79"
+                + "\u7A7A\u7A7F\u7A81\u7A97\u7AA9\u7AAE\u7AB6\u7ACB\u7AD9\u7ADF"
+                + "\u7AE0\u7AE5\u7AEF\u7AF6\u7AF9\u7B11\u7B1B\u7B26\u7B28\u7B2C"
+                + "\u7B46\u7B49\u7B4B\u7B54\u7B56\u7B80\u7B97\u7BA1\u7BAD\u7BB1"
+                + "\u7BC0\u7BC4\u7BC7\u7BC9\u7C21\u7C2B\u7C3D\u7C3F\u7C43\u7C4C"
+                + "\u7C4D\u7C64\u7C73\u7C89\u7C97\u7CB5\u7CBE\u7CCA\u7CD5\u7CDF"
+                + "\u7CFB\u7CFE\u7D00\u7D04\u7D05\u7D0D\u7D10\u7D14\u7D19-\u7D1B"
+                + "\u7D20\u7D22\u7D2B\u7D2F\u7D30\u7D39\u7D42\u7D44\u7D50\u7D55"
+                + "\u7D61\u7D66\u7D71\u7D72\u7D93\u7D9C\u7DA0\u7DAD\u7DB1\u7DB2"
+                + "\u7DCA\u7DD2\u7DDA\u7DE3\u7DE8\u7DE9\u7DEC\u7DEF\u7DF4\u7E1B"
+                + "\u7E23\u7E2E\u7E31\u7E3D\u7E3E\u7E41\u7E46\u7E54\u7E5E\u7E6A"
+                + "\u7E73\u7E7C\u7E8C\u7F38\u7F3A\u7F55\u7F6A\u7F6E\u7F70\u7F72"
+                + "\u7F75\u7F77\u7F85\u7F8A\u7F8E\u7F9E\u7FA4\u7FA9\u7FBD\u7FC1"
+                + "\u7FD2\u7FD4\u7FF0\u7FF9\u7FFB\u7FFC\u8000\u8001\u8003\u8005"
+                + "\u800C\u800D\u8010\u8017\u8033\u8036\u804A\u8056\u805A\u805E"
+                + "\u806F\u8070\u8072\u8077\u807D\u8089\u809A\u80A1\u80A5\u80A9"
+                + "\u80AF\u80B2\u80CC\u80CE\u80D6\u80DE\u80E1\u80F8\u80FD\u8106"
+                + "\u812B\u8153\u8154\u8166\u8170\u8173\u817F\u81BD\u81C9\u81D8"
+                + "\u81E3\u81E5\u81E8\u81EA\u81ED\u81F3\u81F4\u81FA\u8207-\u820A"
+                + "\u820C\u820D\u8212\u821E\u821F\u822A\u822C\u8239\u8266\u826F"
+                + "\u8272\u827E\u8292\u829D\u82AC\u82B1\u82B3\u82D7\u82E5\u82E6"
+                + "\u82F1\u8305\u8328\u832B\u8332\u8336\u8349\u8352\u8377\u837C"
+                + "\u8389\u838A\u838E\u83AB\u83DC\u83E9\u83EF\u83F2\u8404\u840A"
+                + "\u842C\u843D\u8449\u8457\u845B\u8461\u8482\u8499\u84B2\u84BC"
+                + "\u84CB\u84EC\u84EE\u8515\u8521\u8523\u856D\u8584\u85A6\u85A9"
+                + "\u85AA\u85C9\u85CD\u85CF\u85DD\u85E4\u85E5\u8606\u8607\u862D"
+                + "\u864E\u8655\u865B\u865F\u8667\u86A9\u86C7\u86CB\u86D9\u8700"
+                + "\u8702\u871C\u8776\u878D\u87A2\u87F2\u87F9\u880D\u883B\u8840"
+                + "\u884C\u8853\u8857\u885B\u885D\u8861\u8863\u8868\u888B\u88AB"
+                + "\u88C1\u88C2\u88D5\u88D8\u88DC\u88DD\u88E1\u88FD\u8907\u8932"
+                + "\u897F\u8981\u8986\u898B\u898F\u8996\u89AA\u89BA\u89BD\u89C0"
+                + "\u89D2\u89E3\u89F8\u8A00\u8A02\u8A08\u8A0A\u8A0E\u8A13\u8A17"
+                + "\u8A18\u8A25\u8A2A\u8A2D\u8A31\u8A34\u8A3B\u8A3C\u8A55\u8A5E"
+                + "\u8A62\u8A66\u8A69\u8A71-\u8A73\u8A87\u8A8C\u8A8D\u8A93\u8A95\u8A9E"
+                + "\u8AA0\u8AA4\u8AAA\u8AB0\u8AB2\u8ABC\u8ABF\u8AC7\u8ACB\u8AD2"
+                + "\u8AD6\u8AF8\u8AFA\u8AFE\u8B00\u8B02\u8B1B\u8B1D\u8B2C\u8B49"
+                + "\u8B58\u8B5C\u8B66\u8B6F\u8B70\u8B77\u8B7D\u8B80\u8B8A\u8B93"
+                + "\u8B9A\u8C37\u8C46\u8C48\u8C50\u8C61\u8C6A\u8C6C\u8C8C\u8C93"
+                + "\u8C9D\u8C9E\u8CA0-\u8CA2\u8CA8\u8CAA-\u8CAC\u8CB4\u8CB7\u8CBB\u8CBC"
+                + "\u8CC0\u8CC7\u8CC8\u8CD3\u8CDC\u8CDE\u8CE2-\u8CE4\u8CE6\u8CEA"
+                + "\u8CED\u8CF4\u8CFA\u8CFC\u8CFD\u8D08\u8D0A\u8D0F\u8D64\u8D6B"
+                + "\u8D70\u8D77\u8D85\u8D8A\u8D95\u8D99\u8DA3\u8DA8\u8DB3\u8DCC"
+                + "\u8DCE\u8DD1\u8DDD\u8DDF\u8DE1\u8DEF\u8DF3\u8E0F\u8E22\u8E5F"
+                + "\u8E64\u8E8D\u8EAB\u8EB2\u8ECA\u8ECC\u8ECD\u8ED2\u8EDF\u8F03"
+                + "\u8F09\u8F14\u8F15\u8F1B\u8F1D\u8F29\u8F2A\u8F2F\u8F38\u8F49"
+                + "\u8F5F\u8F9B\u8FA6\u8FA8\u8FAD\u8FAF-\u8FB2\u8FC5\u8FCE\u8FD1\u8FD4"
+                + "\u8FE6\u8FEA\u8FEB\u8FF0\u8FF4\u8FF7\u8FFD\u9000\u9001\u9003"
+                + "\u9006\u900F\u9010\u9014\u9019-\u901B\u901D\u901F\u9020\u9022"
+                + "\u9023\u9031\u9032\u9038\u903C\u9047\u904A\u904B\u904D\u904E"
+                + "\u9053-\u9055\u9059\u905C\u9060\u9069\u906D\u906E\u9072\u9077"
+                + "\u9078\u907A\u907F-\u9081\u9084\u908A\u908F\u90A3\u90A6\u90AA"
+                + "\u90B1\u90CE\u90E8\u90ED\u90F5\u90FD\u9102\u9109\u912D\u9130"
+                + "\u9149\u914B\u914D\u9152\u9177\u9178\u9189\u9192\u919C\u91AB"
+                + "\u91C7\u91CB-\u91CF\u91D1\u91DD\u91E3\u9234\u9262\u9280\u9285\u9296"
+                + "\u9298\u92B3\u92B7\u92D2\u92FC\u9304\u9322\u9326\u932B\u932F"
+                + "\u934B\u9375\u937E\u938A\u9396\u93AE\u93E1\u9418\u9435\u9451"
+                + "\u9577\u9580\u9583\u9589\u958B\u958F\u9592\u9593\u95A3\u95B1"
+                + "\u95C6\u95CA\u95CD\u95D0\u95DC\u95E1\u9632\u963B\u963F\u9640"
+                + "\u9644\u964D\u9650\u9662-\u9664\u966A\u9670\u9673\u9675-\u9678"
+                + "\u967D\u9686\u968A\u968E\u9694\u969B\u969C\u96A8\u96AA\u96B1"
+                + "\u96B4\u96BB\u96C4-\u96C6\u96C9\u96D6\u96D9\u96DC\u96DE\u96E2"
+                + "\u96E3\u96E8\u96EA\u96F2\u96F6\u96F7\u96FB\u9700\u9707\u970D"
+                + "\u9727\u9732\u9738\u9739\u9742\u9748\u9752\u9756\u975C\u975E"
+                + "\u9760\u9762\u9769\u977C\u978B\u97C3\u97CB\u97D3\u97F3\u97FB"
+                + "\u97FF\u9801\u9802\u9805\u9806\u9808\u9810\u9811\u9813\u9817"
+                + "\u9818\u981E\u982D\u983B\u9846\u984C\u984D\u984F\u9858\u985E"
+                + "\u9867\u986F\u98A8\u98C4\u98DB\u98DF\u98EF\u98F2\u98FD\u98FE"
+                + "\u9905\u990A\u9910\u9918\u9928\u9996\u9999\u99AC\u99D0\u99D5"
+                + "\u99DB\u9A0E\u9A19\u9A37\u9A45\u9A57\u9A5A\u9AA8\u9AD4\u9AD8"
+                + "\u9AEE\u9B06\u9B25\u9B27\u9B31\u9B3C\u9B41\u9B42\u9B45\u9B54"
+                + "\u9B5A\u9B6F\u9BAE\u9CE5\u9CF3\u9CF4\u9D3B\u9D5D\u9DF9\u9E7F"
+                + "\u9E97\u9EA5\u9EB5\u9EBB\u9EBC\u9EC3\u9ECE\u9ED1\u9ED8\u9EDE"
+                + "\u9EE8\u9F13\u9F20\u9F3B\u9F4A\u9F4B\u9F52\u9F61\u9F8D\u9F9C"
+                + "\uFE30-\uFE44\uFE49-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B"
+                + "\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20"
+                + "\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D]"));
+        EXEMPLAR_MAP.put("yo", new UnicodeSet("[{\u0254\u0300}{\u025B\u0300}]"));
+        EXEMPLAR_MAP.put("zgh", new UnicodeSet("[\u2D30\u2D31\u2D33\u2D37\u2D39\u2D3B-\u2D3D"
+                + "\u2D40\u2D43-\u2D45\u2D47\u2D49\u2D4A\u2D4D-\u2D4F\u2D53-\u2D56\u2D59-\u2D5C"
+                + "\u2D5F\u2D61-\u2D63\u2D65{\u2D33\u2D6F}{\u2D3D\u2D6F}]"));
+        EXEMPLAR_MAP.put("zh-Hans", new UnicodeSet("[\u2015\u2016\u2025\u2030\u2035\u203B"
+                + "\u3001-\u3003\u3008-\u3011\u3014-\u3017\u301D\u301E\u4E00\u4E01\u4E03"
+                + "\u4E07-\u4E0E\u4E11\u4E13\u4E14\u4E16\u4E18-\u4E1A\u4E1C\u4E1D\u4E22\u4E24\u4E25"
+                + "\u4E27\u4E2A\u4E2D\u4E30\u4E32\u4E34\u4E38-\u4E3B\u4E3D\u4E3E\u4E43\u4E45"
+                + "\u4E48\u4E49\u4E4B-\u4E50\u4E54\u4E56\u4E58\u4E59\u4E5D\u4E5F-\u4E61"
+                + "\u4E66\u4E70\u4E71\u4E7E\u4E86\u4E88\u4E89\u4E8B\u4E8C\u4E8E"
+                + "\u4E8F\u4E91\u4E92\u4E94\u4E95\u4E9A\u4E9B\u4EA1\u4EA4-\u4EA8"
+                + "\u4EAB\u4EAC\u4EAE\u4EB2\u4EBA\u4EBF-\u4EC2\u4EC5\u4EC7\u4ECA\u4ECB"
+                + "\u4ECD\u4ECE\u4ED4\u4ED6\u4ED8\u4ED9\u4EE3-\u4EE5\u4EEA\u4EEC"
+                + "\u4EF0\u4EF2\u4EF6\u4EF7\u4EFB\u4EFD\u4EFF\u4F01\u4F0A\u4F0D"
+                + "\u4F0F-\u4F11\u4F17-\u4F1A\u4F1F\u4F20\u4F24\u4F26\u4F2F\u4F30"
+                + "\u4F34\u4F38\u4F3C\u4F3D\u4F46\u4F4D-\u4F51\u4F53\u4F55\u4F59\u4F5B"
+                + "\u4F5C\u4F60\u4F64\u4F69\u4F73\u4F7F\u4F8B\u4F9B\u4F9D\u4FA0"
+                + "\u4FA3\u4FA6-\u4FA8\u4FAC\u4FAF\u4FB5\u4FBF\u4FC3\u4FC4\u4FCA\u4FD7"
+                + "\u4FDD\u4FE1\u4FE9\u4FEE\u4FF1\u4FFE\u500D\u5012\u5019\u501A"
+                + "\u501F\u5026\u503C\u503E\u5047\u504C\u504F\u505A\u505C\u5065"
+                + "\u5076\u5077\u5088\u50A3\u50A8\u50AC\u50B2\u50BB\u50CF\u50E7"
+                + "\u50F3\u5112\u513F\u5141\u5143-\u5146\u5148\u5149\u514B\u514D"
+                + "\u5151\u5154\u515A\u5165\u5168\u516B-\u516E\u5170\u5171\u5173-\u5179"
+                + "\u517B-\u517D\u5185\u5188\u518C\u518D\u5192\u5199\u519B\u519C"
+                + "\u51A0\u51AC\u51B0\u51B2\u51B3\u51B5\u51B7\u51C6\u51CC\u51CF"
+                + "\u51DD\u51E0\u51E1\u51E4\u51ED\u51EF\u51F0\u51FA\u51FB\u51FD"
+                + "\u5200\u5206\u5207\u520A\u5211\u5212\u5217-\u521B\u521D\u5224"
+                + "\u5229\u522B\u5230\u5236-\u5238\u523A\u523B\u5242\u524D\u5251\u5267"
+                + "\u5269\u526A\u526F\u5272\u529B\u529D-\u52A1\u52A3\u52A8-\u52AB"
+                + "\u52B1-\u52B3\u52BF\u52C7\u52C9\u52CB\u52D2\u52E4\u52FE\u52FF"
+                + "\u5305\u5306\u5308\u5316\u5317\u5319\u5339-\u533B\u5341\u5343"
+                + "\u5347\u5348\u534A\u534E\u534F\u5351-\u5353\u5355-\u5357\u535A\u535E"
+                + "\u5360-\u5362\u536B\u536F-\u5371\u5373\u5374\u5377\u5382\u5384-\u5386"
+                + "\u5389\u538B-\u538D\u5398\u539A\u539F\u53BB\u53BF\u53C2\u53C8-\u53CD"
+                + "\u53D1\u53D4\u53D6-\u53D9\u53E3-\u53E6\u53EA-\u53ED\u53EF\u53F0"
+                + "\u53F2\u53F3\u53F6-\u53F9\u5403\u5404\u5408-\u540A\u540C-\u540E"
+                + "\u5410\u5411\u5413\u5415\u5417\u541B\u541D\u541F\u5426\u5427"
+                + "\u542B\u542C\u542F\u5435\u5438\u5439\u543B\u543E\u5440\u5446"
+                + "\u5448\u544A\u5450\u5458\u545C\u5462\u5466\u5468\u5473\u5475"
+                + "\u547C\u547D\u548C\u5496\u54A6-\u54A8\u54AA\u54AC\u54AF\u54B1"
+                + "\u54C0\u54C1\u54C7-\u54C9\u54CD\u54CE\u54DF\u54E5\u54E6\u54E9"
+                + "\u54EA\u54ED\u54F2\u5509\u5510\u5524\u552C\u552E\u552F\u5531"
+                + "\u5537\u5546\u554A\u5561\u5565\u5566\u556A\u5580\u5582\u5584"
+                + "\u5587\u558A\u558F\u5594\u559C\u559D\u55B5\u55B7\u55BB\u55D2"
+                + "\u55E8\u55EF\u5609\u561B\u5634\u563B\u563F\u5668\u56DB\u56DE"
+                + "\u56E0\u56E2\u56ED\u56F0\u56F4\u56FA\u56FD\u56FE\u5706\u5708"
+                + "\u571F\u5723\u5728\u572D\u5730\u5733\u573A\u573E\u5740\u5747"
+                + "\u574E\u5750\u5751\u5757\u575A-\u575D\u5761\u5764\u5766\u576A"
+                + "\u5782\u5783\u578B\u5792\u57C3\u57CB\u57CE\u57D4\u57DF\u57F9"
+                + "\u57FA\u5802\u5806\u5815\u5821\u5824\u582A\u5851\u5854\u585E"
+                + "\u586B\u5883\u589E\u58A8\u58C1\u58E4\u58EB\u58EC\u58EE\u58F0"
+                + "\u5904\u5907\u590D\u590F\u5915\u5916\u591A\u591C\u591F\u5925"
+                + "\u5927\u5929-\u592B\u592E\u5931\u5934\u5937-\u593A\u5947-\u5949"
+                + "\u594B\u594E\u594F\u5951\u5954\u5956\u5957\u5965\u5973\u5974"
+                + "\u5976\u5979\u597D\u5982\u5987\u5988\u5996\u5999\u59A5\u59A8"
+                + "\u59AE\u59B9\u59BB\u59C6\u59CA\u59CB\u59D0\u59D1\u59D3\u59D4"
+                + "\u59FF\u5A01\u5A03\u5A04\u5A18\u5A1C\u5A1F\u5A31\u5A46\u5A5A"
+                + "\u5A92\u5AC1\u5ACC\u5AE9\u5B50\u5B54\u5B55\u5B57-\u5B59\u5B5C\u5B5D"
+                + "\u5B5F\u5B63\u5B64\u5B66\u5B69\u5B81\u5B83\u5B87-\u5B89\u5B8B\u5B8C"
+                + "\u5B8F\u5B97-\u5B9E\u5BA1-\u5BA4\u5BAA\u5BB3\u5BB4\u5BB6"
+                + "\u5BB9\u5BBD-\u5BBF\u5BC2\u5BC4-\u5BC7\u5BCC\u5BD2\u5BDD-\u5BDF"
+                + "\u5BE1\u5BE8\u5BF8\u5BF9\u5BFB\u5BFC\u5BFF\u5C01\u5C04\u5C06"
+                + "\u5C0A\u5C0F\u5C11\u5C14\u5C16\u5C18\u5C1A\u5C1D\u5C24\u5C31"
+                + "\u5C3A\u5C3C-\u5C3E\u5C40-\u5C42\u5C45\u5C4B\u5C4F\u5C55\u5C5E\u5C60"
+                + "\u5C71\u5C7F\u5C81\u5C82\u5C97\u5C98\u5C9A\u5C9B\u5CB3\u5CB8"
+                + "\u5CE1\u5CF0\u5D07\u5D29\u5D34\u5DDD\u5DDE\u5DE1\u5DE5-\u5DE8"
+                + "\u5DEB\u5DEE\u5DF1-\u5DF4\u5DF7\u5DFD\u5E01-\u5E03\u5E05\u5E08"
+                + "\u5E0C\u5E10\u5E15\u5E16\u5E1D\u5E26\u5E2D\u5E2E\u5E38\u5E3D"
+                + "\u5E45\u5E55\u5E72-\u5E74\u5E76\u5E78\u5E7B-\u5E7D\u5E7F\u5E86"
+                + "\u5E8A\u5E8F\u5E93-\u5E95\u5E97\u5E99\u5E9A\u5E9C\u5E9E\u5E9F"
+                + "\u5EA6\u5EA7\u5EAD\u5EB7\u5EB8\u5EC9\u5ED6\u5EF6\u5EF7\u5EFA"
+                + "\u5F00\u5F02-\u5F04\u5F0A\u5F0F\u5F15\u5F17\u5F18\u5F1F\u5F20\u5F25"
+                + "\u5F26\u5F2F\u5F31\u5F39\u5F3A\u5F52\u5F53\u5F55\u5F5D\u5F62"
+                + "\u5F69\u5F6C\u5F6D\u5F70\u5F71\u5F77\u5F79\u5F7B\u5F7C\u5F80"
+                + "\u5F81\u5F84\u5F85\u5F88\u5F8B\u5F8C\u5F90\u5F92\u5F97\u5FAA"
+                + "\u5FAE\u5FB5\u5FB7\u5FC3\u5FC5\u5FC6\u5FCC\u5FCD\u5FD7-\u5FD9"
+                + "\u5FE0\u5FE7\u5FEB\u5FF5\u5FFD\u6000\u6001\u600E\u6012\u6015"
+                + "\u6016\u601D\u6021\u6025\u6027\u6028\u602A\u603B\u604B\u6050"
+                + "\u6062\u6068\u6069\u606D\u606F\u6070\u6076\u607C\u6084\u6089"
+                + "\u6094\u609F\u60A0\u60A3\u60A8\u60B2\u60C5\u60D1\u60DC\u60E0"
+                + "\u60E7\u60E8\u60EF\u60F3\u60F9\u6101\u6108\u6109\u610F\u611A"
+                + "\u611F\u6127\u6148\u614E\u6155\u6162\u6167\u6170\u61BE\u61C2"
+                + "\u61D2\u6208\u620A\u620C\u620F-\u6212\u6216\u6218\u622A\u6234"
+                + "\u6237\u623F-\u6241\u6247\u624B\u624D\u624E\u6251\u6253\u6258\u6263"
+                + "\u6267\u6269\u626B-\u626F\u6279\u627E-\u6280\u6284\u628A\u6291\u6293"
+                + "\u6295\u6297\u6298\u62A2\u62A4\u62A5\u62AB\u62AC\u62B1\u62B5"
+                + "\u62B9\u62BD\u62C5\u62C6\u62C9\u62CD\u62D2\u62D4\u62D6\u62D8"
+                + "\u62DB\u62DC\u62DF\u62E5\u62E6\u62E8\u62E9\u62EC\u62F3\u62F7"
+                + "\u62FC\u62FE\u62FF\u6301\u6307\u6309\u6311\u6316\u631D\u6321"
+                + "\u6324\u6325\u632A\u632F\u633A\u6349\u6350\u6355\u635F\u6361"
+                + "\u6362\u636E\u6377\u6388\u6389\u638C\u6392\u63A2\u63A5\u63A7-\u63AA"
+                + "\u63B8\u63CF\u63D0\u63D2\u63E1\u63F4\u641C\u641E\u642C\u642D"
+                + "\u6444\u6446\u644A\u6454\u6458\u6469\u6478\u6492\u649E\u64A4"
+                + "\u64AD\u64CD\u64CE\u64E6\u652F\u6536\u6539\u653B\u653E\u653F"
+                + "\u6545\u6548\u654C\u654F\u6551\u6559\u655D\u6562\u6563\u6566"
+                + "\u656C\u6570\u6572\u6574\u6587\u658B\u6590\u6597\u6599\u659C"
+                + "\u65A5\u65AD\u65AF\u65B0\u65B9\u65BC\u65BD\u65C1\u65C5\u65CB"
+                + "\u65CF\u65D7\u65E0\u65E2\u65E5-\u65E9\u65ED\u65F6\u65FA\u6602"
+                + "\u6606\u660C\u660E\u660F\u6613\u661F\u6620\u6625\u6628\u662D"
+                + "\u662F\u663E\u6643\u664B\u6652\u6653\u665A\u6668\u666E\u666F"
+                + "\u6674\u6676\u667A\u6682\u6691\u6696\u6697\u66AE\u66B4\u66F0"
+                + "\u66F2\u66F4\u66F9\u66FC\u66FE-\u6700\u6708\u6709\u670B\u670D"
+                + "\u6717\u671B\u671D\u671F\u6728\u672A-\u672D\u672F\u6731\u6735\u673A"
+                + "\u6740\u6742\u6743\u6749\u674E\u6750\u6751\u675C\u675F\u6761"
+                + "\u6765\u6768\u676F\u6770\u677E\u677F\u6781\u6784\u6790\u6797"
+                + "\u679C\u679D\u67A2\u67AA\u67AB\u67B6\u67CF\u67D0\u67D3\u67D4"
+                + "\u67E5\u67EC\u67EF\u67F3\u67F4\u6807\u680B\u680F\u6811\u6821"
+                + "\u6837-\u6839\u683C\u6843\u6846\u6848\u684C\u6851\u6863\u6865"
+                + "\u6881\u6885\u68A6\u68AF\u68B0\u68B5\u68C0\u68C9\u68CB\u68D2"
+                + "\u68DA\u68EE\u6905\u690D\u6930\u6954\u695A\u6960\u697C\u6982"
+                + "\u699C\u6A21\u6A31\u6A80\u6B20-\u6B23\u6B27\u6B32\u6B3A\u6B3E"
+                + "\u6B49\u6B4C\u6B62-\u6B66\u6B6A\u6B7B\u6B8A\u6B8B\u6BB5\u6BC5"
+                + "\u6BCD\u6BCF\u6BD2\u6BD4\u6BD5\u6BDB\u6BEB\u6C0F\u6C11\u6C14"
+                + "\u6C1B\u6C34\u6C38\u6C42\u6C47\u6C49\u6C57\u6C5D\u6C5F-\u6C61"
+                + "\u6C64\u6C6A\u6C76\u6C7D\u6C83\u6C88\u6C89\u6C99\u6C9F\u6CA1"
+                + "\u6CA7\u6CB3\u6CB9\u6CBB\u6CBF\u6CC9\u6CCA\u6CD5\u6CDB\u6CE1-\u6CE3"
+                + "\u6CE5\u6CE8\u6CF0\u6CF3\u6CFD\u6D0B\u6D17\u6D1B\u6D1E\u6D25"
+                + "\u6D2A\u6D32\u6D3B\u6D3D\u6D3E\u6D41\u6D45\u6D4B\u6D4E\u6D4F"
+                + "\u6D51\u6D53\u6D59\u6D66\u6D69\u6D6A\u6D6E\u6D74\u6D77\u6D85"
+                + "\u6D88\u6D89\u6D9B\u6DA8\u6DAF\u6DB2\u6DB5\u6DCB\u6DD1\u6DD8"
+                + "\u6DE1\u6DF1\u6DF7\u6DFB\u6E05\u6E10\u6E21\u6E23\u6E29\u6E2F"
+                + "\u6E34\u6E38\u6E56\u6E7E\u6E90\u6E9C\u6EAA\u6ECB\u6ED1\u6ED5"
+                + "\u6EE1\u6EE5\u6EE8\u6EF4\u6F02\u6F0F\u6F14\u6F20\u6F2B\u6F58"
+                + "\u6F5C\u6F6E\u6F8E\u6FB3\u6FC0\u704C\u706B\u706D\u706F\u7070"
+                + "\u7075\u707F\u7089\u708E\u70AE\u70B8\u70B9\u70C2\u70C8\u70E4"
+                + "\u70E6\u70E7\u70ED\u7126\u7136\u714C\u715E\u7167\u716E\u718A"
+                + "\u719F\u71C3\u71D5\u7206\u722A\u722C\u7231\u7235-\u7238\u723D\u7247"
+                + "\u7248\u724C\u7259\u725B\u7261\u7262\u7267\u7269\u7272\u7275"
+                + "\u7279\u727A\u72AF\u72B6\u72B9\u72C2\u72D0\u72D7\u72E0\u72EC"
+                + "\u72EE\u72F1\u72FC\u731B\u731C\u732A\u732E\u7334\u7384\u7387"
+                + "\u7389\u738B\u739B\u73A9\u73AB\u73AF\u73B0\u73B2\u73BB\u73C0"
+                + "\u73CA\u73CD\u73E0\u73ED\u7403\u7406\u740A\u742A\u7433\u7434"
+                + "\u743C\u7459\u745A\u745C\u745E\u745F\u7470\u7476\u7483\u74DC"
+                + "\u74E6\u74F6\u7518\u751A\u751C\u751F\u7528\u752B\u7530-\u7533"
+                + "\u7535\u7537\u7538\u753B\u7545\u754C\u7559\u7565\u756A\u7586"
+                + "\u758F\u7591\u7597\u75AF\u75B2\u75BC\u75BE\u75C5\u75D5\u75DB"
+                + "\u75F4\u7678\u767B\u767D\u767E\u7684\u7686\u7687\u76AE\u76C8"
+                + "\u76CA\u76D1\u76D2\u76D6\u76D8\u76DB\u76DF\u76EE\u76F2\u76F4"
+                + "\u76F8\u76FC\u76FE\u7701\u7709\u770B\u771F\u7720\u773C\u7740"
+                + "\u775B\u7761\u7763\u77A7\u77DB\u77E3\u77E5\u77ED\u77F3\u77F6"
+                + "\u7801\u7802\u780D\u7814\u7834\u7840\u7855\u786C\u786E\u788D"
+                + "\u788E\u7891\u7897\u789F\u78A7\u78B0\u78C1\u78C5\u78E8\u793A"
+                + "\u793C\u793E\u7956\u795A\u795D\u795E\u7965\u7968\u796F\u7978"
+                + "\u7981\u7984\u7985\u798F\u79BB\u79C0\u79C1\u79CB\u79CD\u79D1"
+                + "\u79D2\u79D8\u79DF\u79E4\u79E6\u79E9\u79EF\u79F0\u79FB\u7A00"
+                + "\u7A0B\u7A0D\u7A0E\u7A23\u7A33\u7A3F\u7A46\u7A76\u7A77\u7A79"
+                + "\u7A7A\u7A7F\u7A81\u7A97\u7A9D\u7ACB\u7AD9\u7ADE-\u7AE0\u7AE5\u7AEF"
+                + "\u7AF9\u7B11\u7B14\u7B1B\u7B26\u7B28\u7B2C\u7B49\u7B4B\u7B51"
+                + "\u7B54\u7B56\u7B79\u7B7E\u7B80\u7B97\u7BA1\u7BAD\u7BB1\u7BC7"
+                + "\u7BEE\u7C3F\u7C4D\u7C73\u7C7B\u7C89\u7C92\u7C97\u7C9F\u7CA4"
+                + "\u7CB9\u7CBE\u7CCA\u7CD5\u7CD6\u7CDF\u7CFB\u7D20\u7D22\u7D27"
+                + "\u7D2B\u7D2F\u7E41\u7EA2\u7EA6\u7EA7\u7EAA\u7EAF\u7EB2\u7EB3"
+                + "\u7EB5\u7EB7\u7EB8\u7EBD\u7EBF\u7EC3\u7EC4\u7EC6-\u7EC8\u7ECD\u7ECF"
+                + "\u7ED3\u7ED5\u7ED8\u7ED9\u7EDC\u7EDD\u7EDF\u7EE7\u7EE9\u7EEA"
+                + "\u7EED\u7EF4\u7EF5\u7EFC\u7EFF\u7F05\u7F13\u7F16\u7F18\u7F20"
+                + "\u7F29\u7F34\u7F36\u7F38\u7F3A\u7F50\u7F51\u7F55\u7F57\u7F5A"
+                + "\u7F62\u7F6A\u7F6E\u7F72\u7F8A\u7F8E\u7F9E\u7FA4\u7FAF\u7FBD"
+                + "\u7FC1\u7FC5\u7FD4\u7FD8\u7FE0\u7FF0\u7FFB\u7FFC\u8000\u8001"
+                + "\u8003\u8005\u800C\u800D\u8010\u8017\u8033\u8036\u804A\u804C"
+                + "\u8054\u8058\u805A\u806A\u8089\u8096\u809A\u80A1\u80A4\u80A5"
+                + "\u80A9\u80AF\u80B2\u80C1\u80C6\u80CC\u80CE\u80D6\u80DC\u80DE"
+                + "\u80E1\u80F6\u80F8\u80FD\u8106\u8111\u811A\u8131\u8138\u814A"
+                + "\u8150\u8153\u8170\u8179\u817E\u817F\u81C2\u81E3\u81EA\u81ED"
+                + "\u81F3\u81F4\u820C\u820D\u8212\u821E\u821F\u822A\u822C\u8230"
+                + "\u8239\u826E\u826F\u8272\u827A\u827E\u8282\u8292\u829D\u82A6"
+                + "\u82AC\u82AD\u82B1\u82B3\u82CD\u82CF\u82D7\u82E5\u82E6\u82F1"
+                + "\u8302\u8303\u8328\u832B\u8336\u8349\u8350\u8352\u8363\u836F"
+                + "\u8377\u8389\u838E\u83AA\u83AB\u83B1\u83B2\u83B7\u83DC\u83E9"
+                + "\u83F2\u8404\u840D\u8424\u8425\u8427\u8428\u843D\u8457\u845B"
+                + "\u8461\u8482\u848B\u8499\u84C9\u84DD\u84EC\u8511\u8521\u8584"
+                + "\u85AA\u85C9\u85CF\u85E4\u864E\u8651\u866B\u8679\u867D\u867E"
+                + "\u8681\u86C7\u86CB\u86D9\u86EE\u8702\u871C\u8776\u878D\u87F9"
+                + "\u8822\u8840\u884C\u8857\u8861\u8863\u8865\u8868\u888B\u88AB"
+                + "\u88AD\u88C1\u88C2\u88C5\u88D5\u88E4\u897F\u8981\u8986\u89C1"
+                + "\u89C2\u89C4\u89C6\u89C8\u89C9\u89D2\u89E3\u8A00\u8A89\u8A93"
+                + "\u8B66\u8BA1\u8BA2\u8BA4\u8BA8\u8BA9\u8BAD-\u8BB0\u8BB2\u8BB7"
+                + "\u8BB8\u8BBA\u8BBE\u8BBF\u8BC1\u8BC4\u8BC6\u8BC9\u8BCD\u8BD1"
+                + "\u8BD5\u8BD7\u8BDA\u8BDD\u8BDE\u8BE2\u8BE5\u8BE6\u8BED\u8BEF"
+                + "\u8BF4\u8BF7\u8BF8\u8BFA\u8BFB\u8BFE\u8C01\u8C03\u8C05\u8C08"
+                + "\u8C0A\u8C0B\u8C13\u8C1C\u8C22\u8C28\u8C2C\u8C31\u8C37\u8C46"
+                + "\u8C61\u8C6A\u8C8C\u8D1D-\u8D1F\u8D21-\u8D25\u8D27-\u8D2A\u8D2D\u8D2F"
+                + "\u8D31\u8D34\u8D35\u8D38-\u8D3A\u8D3C\u8D3E\u8D44\u8D4B\u8D4C\u8D4F"
+                + "\u8D50\u8D54\u8D56\u8D5A\u8D5B\u8D5E\u8D60\u8D62\u8D64\u8D6B"
+                + "\u8D70\u8D75\u8D77\u8D81\u8D85\u8D8A\u8D8B\u8DA3\u8DB3\u8DC3"
+                + "\u8DCC\u8DD1\u8DDD\u8DDF\u8DEF\u8DF3\u8E0F\u8E22\u8E29\u8EAB"
+                + "\u8EB2\u8F66\u8F68\u8F69\u8F6C\u8F6E-\u8F70\u8F7B\u8F7D\u8F83\u8F85"
+                + "\u8F86\u8F88\u8F89\u8F91\u8F93\u8F9B\u8F9E\u8FA8\u8FA9\u8FB0"
+                + "\u8FB1\u8FB9\u8FBE\u8FC1\u8FC5\u8FC7\u8FC8\u8FCE\u8FD0\u8FD1"
+                + "\u8FD4\u8FD8\u8FD9\u8FDB-\u8FDF\u8FE6\u8FEA\u8FEB\u8FF0\u8FF7\u8FFD"
+                + "\u9000-\u9003\u9006\u9009\u900A\u900F\u9010\u9012\u9014\u901A"
+                + "\u901B\u901D\u901F\u9020\u9022\u9038\u903B\u903C\u9047\u904D"
+                + "\u9053\u9057\u906D\u906E\u9075\u907F\u9080\u9093\u90A3\u90A6"
+                + "\u90AA\u90AE\u90B1\u90BB\u90CE\u90D1\u90E8\u90ED\u90FD\u9102"
+                + "\u9149\u914B\u914D\u9152\u9177\u9178\u9189\u9192\u91C7\u91CA"
+                + "\u91CC-\u91CF\u91D1\u9488\u9493\u949F\u94A2\u94A6\u94AF\u94B1"
+                + "\u94BB\u94C1-\u94C3\u94DC\u94E2\u94ED\u94F6\u94FA\u94FE\u9500\u9501"
+                + "\u9505\u950B\u9511\u9519\u9521\u9526\u952E\u953A\u9547\u9551"
+                + "\u955C\u956D\u957F\u95E8\u95EA\u95ED\u95EE\u95F0\u95F2\u95F4"
+                + "\u95F7\u95F9\u95FB\u9601\u9605\u9610\u9614\u961F\u962E\u9632-\u9636"
+                + "\u963B\u963F\u9640\u9644-\u9646\u9648\u964D\u9650\u9662\u9664\u9669"
+                + "\u966A\u9675-\u9677\u9686\u968F\u9690\u9694\u969C\u96BE\u96C4-\u96C6"
+                + "\u96C9\u96E8\u96EA\u96EF\u96F3\u96F6\u96F7\u96FE\u9700\u9707"
+                + "\u970D\u9716\u9732\u9738\u9739\u9752\u9756\u9759\u975E\u9760"
+                + "\u9762\u9769\u977C\u978B\u9791\u97E6\u97E9\u97F3\u9875\u9876"
+                + "\u9879-\u987B\u987D-\u987F\u9884\u9886\u9887\u9891\u9897\u9898"
+                + "\u989D\u98CE\u98D8\u98D9\u98DE\u98DF\u9910\u996D\u996E\u9970"
+                + "\u9971\u997C\u9986\u9996\u9999\u99A8\u9A6C\u9A71\u9A76\u9A7B"
+                + "\u9A7E\u9A8C\u9A91\u9A97\u9A9A\u9AA4\u9AA8\u9AD8\u9B3C\u9B41"
+                + "\u9B42\u9B45\u9B54\u9C7C\u9C81\u9C9C\u9E1F\u9E21\u9E23\u9E2D"
+                + "\u9E3F\u9E45\u9E64\u9E70\u9E7F\u9EA6\u9EBB\u9EC4\u9ECE\u9ED1"
+                + "\u9ED8\u9F13\u9F20\u9F3B\u9F50\u9F7F\u9F84\u9F99\u9F9F\uFE30"
+                + "\uFE31\uFE33-\uFE44\uFE49-\uFE52\uFE54-\uFE57\uFE59-\uFE61\uFE63\uFE68"
+                + "\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B"
+                + "\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D]"));
+        EXEMPLAR_MAP.put("zh-Hant", new UnicodeSet("[\u2025\u2027\u2030\u2035\u203B\u203E"
+                + "\u3001-\u3003\u3008-\u3011\u3014\u3015\u301D\u301E\u4E00\u4E01\u4E03"
+                + "\u4E08-\u4E0D\u4E11\u4E14\u4E16\u4E18\u4E19\u4E1E\u4E1F\u4E26\u4E2D\u4E32\u4E38"
+                + "\u4E39\u4E3B\u4E43\u4E45\u4E48\u4E4B\u4E4D-\u4E4F\u4E56\u4E58\u4E59\u4E5D"
+                + "\u4E5F\u4E7E\u4E82\u4E86\u4E88\u4E8B\u4E8C\u4E8E\u4E91\u4E92"
+                + "\u4E94\u4E95\u4E9B\u4E9E\u4EA1\u4EA4-\u4EA6\u4EA8\u4EAB-\u4EAE"
+                + "\u4EBA\u4EC0-\u4EC2\u4EC7\u4ECA\u4ECB\u4ECD\u4ED4\u4ED6\u4ED8\u4ED9"
+                + "\u4EE3-\u4EE5\u4EF0\u4EF2\u4EF6\u4EFB\u4EFD\u4F01\u4F0A\u4F0D"
+                + "\u4F0F-\u4F11\u4F19\u4F2F\u4F30\u4F34\u4F38\u4F3C\u4F3D\u4F46"
+                + "\u4F48\u4F49\u4F4D-\u4F50\u4F54\u4F55\u4F59\u4F5B\u4F5C\u4F60"
+                + "\u4F69\u4F73\u4F7F\u4F86\u4F8B\u4F9B\u4F9D\u4FAF\u4FB5\u4FB6"
+                + "\u4FBF\u4FC2-\u4FC4\u4FCA\u4FD7\u4FDD\u4FE0\u4FE1\u4FEE\u4FF1\u4FFE"
+                + "\u500B\u500D\u5011\u5012\u5019\u501A\u501F\u502B\u503C\u5047"
+                + "\u5049\u504F\u505A\u505C\u5065\u5074-\u5077\u5080\u5091\u5099\u50A2"
+                + "\u50A3\u50B2\u50B3\u50B7\u50BB\u50BE\u50C5\u50CE\u50CF\u50D1"
+                + "\u50E7\u50F3\u50F5\u50F9\u5100\u5104\u5110\u5112\u5118\u511F"
+                + "\u512A\u5133\u5137\u513B\u5141\u5143-\u5149\u514B-\u514D\u5152\u5154"
+                + "\u5165\u5167-\u5169\u516B-\u516E\u5171\u5175-\u5179\u517C\u518A"
+                + "\u518D\u5192\u51A0\u51AC\u51B0\u51B7\u51C6\u51CC\u51DD\u51E1"
+                + "\u51F0\u51F1\u51FA\u51FD\u5200\u5206\u5207\u520A\u5217\u521D"
+                + "\u5224\u5225\u5229-\u522B\u5230\u5236-\u5238\u523A\u523B\u5247\u524C"
+                + "\u524D\u525B\u5269\u526A\u526F\u5272\u5275\u5283\u5287\u5289"
+                + "\u528D\u529B\u529F\u52A0\u52A9-\u52AB\u52C1\u52C7\u52C9\u52D2"
+                + "\u52D5\u52D9\u52DD\u52DE\u52E2\u52E4\u52F3\u52F5\u52F8\u52FF"
+                + "\u5305\u5308\u5316\u5317\u5339\u5340\u5341\u5343\u5347\u5348"
+                + "\u534A\u5351-\u5354\u5357\u535A\u535C\u535E\u5360\u5361\u536F-\u5371"
+                + "\u5373\u5377\u537B\u5384\u5398\u539A\u539F\u53AD\u53B2\u53BB"
+                + "\u53C3\u53C8\u53CA\u53CB\u53CD\u53D4\u53D6\u53D7\u53E2-\u53E6"
+                + "\u53EA-\u53ED\u53EF\u53F0\u53F2\u53F3\u53F6\u53F8\u5403\u5404"
+                + "\u5408-\u540A\u540C-\u540E\u5410-\u5412\u541B\u541D-\u5420\u5426\u5427"
+                + "\u542B\u5433\u5435\u5438\u5439\u543E\u5440\u5442\u5446\u544A"
+                + "\u5462\u5468\u5473\u5475\u547C\u547D\u548C\u5496\u54A6\u54A7"
+                + "\u54AA\u54AC\u54B1\u54C0\u54C1\u54C7-\u54C9\u54CE\u54E1\u54E5\u54E6"
+                + "\u54E9\u54EA\u54ED\u54F2\u5509\u5510\u5514\u552C\u552E\u552F"
+                + "\u5531\u5537\u5538\u5546\u554A\u554F\u555F\u5561\u5565\u5566"
+                + "\u556A\u5580\u5582\u5584\u5587\u558A\u5594\u559C\u559D\u55AC"
+                + "\u55AE\u55B5\u55CE\u55DA\u55E8\u55EF\u5606\u5609\u5617\u561B"
+                + "\u5634\u563B\u563F\u5668\u5674\u5687\u56B4\u56C9\u56CC\u56D1"
+                + "\u56DB\u56DE\u56E0\u56F0\u56FA\u5708\u570B\u570D\u5712\u5713"
+                + "\u5716\u5718\u571C\u571F\u5728\u572D\u5730\u573E\u5740\u5747"
+                + "\u574E\u5750\u5761\u5764\u5766\u576A\u5782\u5783\u578B\u57C3"
+                + "\u57CE\u57D4\u57DF\u57F7\u57F9\u57FA\u5802\u5805\u5806\u5821"
+                + "\u5824\u582A\u5831\u5834\u584A\u5854\u5857\u585E\u586B\u5875"
+                + "\u5883\u588E\u589E\u58A8\u58AE\u58C1\u58C7\u58D3\u58D8\u58DE"
+                + "\u58E2\u58E4\u58EB\u58EC\u58EF\u58FD\u590F\u5915\u5916\u591A"
+                + "\u591C\u5920\u5922\u5925\u5927\u5929-\u592B\u592E\u5931\u5937\u5938"
+                + "\u593E\u5947-\u5949\u594E\u594F\u5951\u5954\u5957\u5965\u5967\u596A"
+                + "\u596E\u5973\u5974\u5976\u5979\u597D\u5982\u5999\u599D\u59A5"
+                + "\u59A8\u59AE\u59B3\u59B9\u59BB\u59C6\u59CA\u59CB\u59D0\u59D1"
+                + "\u59D3\u59D4\u59FF\u5A01\u5A03\u5A18\u5A1B\u5A41\u5A46\u5A5A"
+                + "\u5A66\u5A92\u5ABD\u5ACC\u5AE9\u5B50\u5B54\u5B57\u5B58\u5B5C"
+                + "\u5B5D\u5B5F\u5B63\u5B64\u5B69\u5B6B\u5B78\u5B83\u5B85\u5B87-\u5B89"
+                + "\u5B8B\u5B8C\u5B8F\u5B97-\u5B9C\u5BA2-\u5BA4\u5BAE\u5BB3\u5BB6\u5BB9"
+                + "\u5BBF\u5BC2\u5BC4-\u5BC6\u5BCC\u5BD2\u5BDE\u5BDF\u5BE2\u5BE6-\u5BE9"
+                + "\u5BEB\u5BEC\u5BEE\u5BF5\u5BF6\u5C01\u5C04\u5C07\u5C08\u5C0A"
+                + "\u5C0B\u5C0D-\u5C0F\u5C11\u5C16\u5C1A\u5C24\u5C31\u5C3A\u5C3C\u5C3E"
+                + "\u5C40\u5C41\u5C45\u5C46\u5C4B\u5C4F\u5C55\u5C60\u5C64\u5C6C"
+                + "\u5C71\u5CA1\u5CA9\u5CB8\u5CC7\u5CF0\u5CF6\u5CFD\u5D07\u5D19"
+                + "\u5D34\u5D50\u5DBA\u5DBC\u5DDD\u5DDE\u5DE1\u5DE5-\u5DE8\u5DEB\u5DEE"
+                + "\u5DF1-\u5DF4\u5DF7\u5DFD\u5E02\u5E03\u5E0C\u5E15\u5E16\u5E1B"
+                + "\u5E1D\u5E25\u5E2B\u5E2D\u5E33\u5E36\u5E38\u5E3D\u5E45\u5E55"
+                + "\u5E63\u5E6B\u5E72-\u5E74\u5E78\u5E79\u5E7B-\u5E7E\u5E87\u5E8A"
+                + "\u5E8F\u5E95\u5E97\u5E9A\u5E9C\u5EA6\u5EA7\u5EAB\u5EAD\u5EB7"
+                + "\u5EB8\u5EC9\u5ED6\u5EE0\u5EE2\u5EE3\u5EF3\u5EF6\u5EF7\u5EFA"
+                + "\u5F04\u5F0F\u5F15\u5F17\u5F18\u5F1F\u5F26\u5F31\u5F35\u5F37"
+                + "\u5F48\u5F4A\u5F4C\u5F4E\u5F5D\u5F5E\u5F62\u5F65\u5F69\u5F6C"
+                + "\u5F6D\u5F70\u5F71\u5F79\u5F7C\u5F80\u5F81\u5F85\u5F88\u5F8B"
+                + "\u5F8C\u5F90-\u5F92\u5F97\u5F9E\u5FA9\u5FAE\u5FB5\u5FB7\u5FB9\u5FC3"
+                + "\u5FC5\u5FCC\u5FCD\u5FD7-\u5FD9\u5FE0\u5FEB\u5FF5\u5FFD\u600E\u6012"
+                + "\u6015\u6016\u601D\u6021\u6025\u6027\u6028\u602A\u6046\u6050"
+                + "\u6062\u6065\u6068\u6069\u606D\u606F\u6070\u6085\u6089\u6094"
+                + "\u609F\u60A0\u60A8\u60B2\u60B6\u60C5\u60D1\u60DC\u60E0\u60E1"
+                + "\u60F1\u60F3\u60F9\u6101\u6108\u6109\u610F\u611A\u611B\u611F"
+                + "\u6148\u614B\u6155\u6158\u6162\u6163\u6167\u616E\u6170\u6176"
+                + "\u617E\u6182\u6190\u6191\u61B2\u61B6\u61BE\u61C2\u61C9\u61F6"
+                + "\u61F7\u61FC\u6200\u6208\u620A\u620C\u6210-\u6212\u6216\u622A"
+                + "\u6230\u6232\u6234\u6236\u623F-\u6241\u6247\u624B\u624D\u624E"
+                + "\u6253\u6258\u6263\u6265\u626D\u626F\u6279\u627E-\u6280\u6284\u628A"
+                + "\u6293\u6295\u6297\u6298\u62AB\u62AC\u62B1\u62B5\u62B9\u62BD"
+                + "\u62C6\u62C9\u62CB\u62CD\u62CF\u62D2\u62D4\u62D6\u62DB\u62DC"
+                + "\u62EC\u62F3\u62FC\u62FE\u62FF\u6301\u6307\u6309\u6311\u6316"
+                + "\u632A\u632F\u633A\u6350\u6355\u6368\u6372\u6377\u6383\u6388"
+                + "\u6389\u638C\u6392\u639B\u63A1\u63A2\u63A5\u63A7\u63A8\u63AA"
+                + "\u63CF\u63D0\u63D2\u63DA\u63DB\u63E1\u63EE\u63F4\u640D\u6416"
+                + "\u641C\u641E\u642C\u642D\u6436\u6458\u6469\u6478\u6490\u6492"
+                + "\u649E\u64A3\u64A5\u64AD\u64BE\u64BF\u64C1\u64C7\u64CA\u64CB"
+                + "\u64CD\u64CE\u64D4\u64DA\u64E0\u64E6\u64EC\u64F4\u64FA\u64FE"
+                + "\u651D\u652F\u6536\u6539\u653B\u653E\u653F\u6545\u6548\u654D"
+                + "\u654F\u6551\u6557-\u6559\u655D\u6562\u6563\u6566\u656C\u6574"
+                + "\u6575\u6578\u6587\u6590\u6597\u6599\u65AF\u65B0\u65B7\u65B9"
+                + "\u65BC\u65BD\u65C1\u65C5\u65CB\u65CF\u65D7\u65E2\u65E5\u65E6"
+                + "\u65E9\u65ED\u65FA\u6602\u6606\u6607\u660C\u660E\u660F\u6613"
+                + "\u661F\u6620\u6625\u6628\u662D\u662F\u6642\u6649\u6652\u665A"
+                + "\u6668\u666E\u666F\u6674\u6676\u667A\u6691\u6696\u6697\u66AB"
+                + "\u66B4\u66C6\u66C9\u66F0\u66F2\u66F4\u66F8\u66FC\u66FE-\u6700"
+                + "\u6703\u6708\u6709\u670B\u670D\u6717\u671B\u671D\u671F\u6728"
+                + "\u672A-\u672D\u6731\u6735\u6749\u674E\u6750\u6751\u675C\u675F"
+                + "\u676F-\u6771\u677E\u677F\u6790\u6797\u679C\u679D\u67B6\u67CF"
+                + "\u67D0\u67D3\u67D4\u67E5\u67EC\u67EF\u67F3\u67F4\u6817\u6821"
+                + "\u6838\u6839\u683C\u6843\u6848\u684C\u6851\u6881\u6885\u689D"
+                + "\u68A8\u68AF\u68B0\u68B5\u68C4\u68C9\u68CB\u68D2\u68DA\u68EE"
+                + "\u6905\u690D\u6930\u694A\u6953\u6954\u695A\u696D\u6975\u6982"
+                + "\u699C\u69AE\u69CB\u69CD\u6A02\u6A13\u6A19\u6A1E\u6A21\u6A23"
+                + "\u6A39\u6A4B\u6A5F\u6A6B\u6A80\u6A94\u6AA2\u6B04\u6B0A\u6B21"
+                + "\u6B23\u6B32\u6B3A\u6B3D\u6B3E\u6B49\u6B4C\u6B50\u6B61-\u6B66"
+                + "\u6B72\u6B77\u6B78\u6B7B\u6B8A\u6B98\u6BB5\u6BBA\u6BBC\u6BC0"
+                + "\u6BC5\u6BCD\u6BCF\u6BD2\u6BD4\u6BDB\u6BEB\u6C0F\u6C11\u6C23"
+                + "\u6C34\u6C38\u6C42\u6C57\u6C5D\u6C5F-\u6C61\u6C6A\u6C76\u6C7A\u6C7D"
+                + "\u6C83\u6C88\u6C89\u6C92\u6C96\u6C99\u6CB3\u6CB9\u6CBB\u6CBF"
+                + "\u6CC1\u6CC9\u6CCA\u6CD5\u6CE1\u6CE2\u6CE5\u6CE8\u6CF0\u6CF3"
+                + "\u6D0B\u6D17\u6D1B\u6D1E\u6D29\u6D2A\u6D32\u6D3B\u6D3D\u6D3E"
+                + "\u6D41\u6D66\u6D69\u6D6A\u6D6E\u6D77\u6D85\u6D87-\u6D89\u6DAF\u6DB2"
+                + "\u6DB5\u6DBC\u6DD1\u6DDA\u6DE1\u6DE8\u6DF1\u6DF7\u6DFA\u6E05"
+                + "\u6E1B\u6E21\u6E2C\u6E2F\u6E38\u6E3E\u6E56\u6E6F\u6E90\u6E96"
+                + "\u6E9D\u6EAA\u6EAB\u6EC4\u6EC5\u6ECB\u6ED1\u6EF4\u6EFE\u6EFF"
+                + "\u6F02\u6F0F\u6F14\u6F20\u6F22\u6F2B\u6F32\u6F38\u6F54\u6F58"
+                + "\u6F5B\u6F6E\u6F8E\u6FA4\u6FB3\u6FC0\u6FC3\u6FDF\u6FE4\u6FEB"
+                + "\u6FF1\u700F\u704C\u7063\u706B\u7070\u707D\u708E\u70AE\u70B8"
+                + "\u70BA\u70C8\u70CF\u70E4\u7121\u7126\u7136\u7159\u715E\u7167"
+                + "\u7169\u718A\u719F\u71B1\u71C3\u71C8\u71D2\u71DF\u71E6\u7206"
+                + "\u7210\u721B\u722A\u722C\u722D\u7235\u7236\u7238\u723A\u723D"
+                + "\u723E\u7246-\u7248\u724C\u7259\u725B\u7260\u7267\u7269\u7272\u7279"
+                + "\u727D\u72A7\u72AF\u72C0\u72C2\u72C4\u72D0\u72D7\u72E0\u72FC"
+                + "\u731B\u731C\u7334\u7336\u7344\u7345\u734E\u7368\u7372\u7378"
+                + "\u737B\u7384\u7387\u7389\u738B\u73A9\u73AB\u73B2\u73BB\u73CA"
+                + "\u73CD\u73E0\u73E5\u73ED\u73FE\u7403\u7406\u7409\u742A\u7433"
+                + "\u7434\u7459\u745A\u745C\u745E\u745F\u7464\u746A\u7470\u74B0"
+                + "\u74DC\u74E6\u74F6\u7518\u751A\u751C\u751F\u7522\u7528\u752B"
+                + "\u7530-\u7533\u7537\u7538\u754C\u7559\u7562\u7565\u756A\u756B"
+                + "\u7570\u7576\u7586\u758F\u7591\u75BC\u75C5\u75D5\u75DB\u75F4"
+                + "\u760B\u7642\u7661\u7678\u767B-\u767E\u7684\u7686\u7687\u76AE"
+                + "\u76C3\u76CA\u76DB\u76DC\u76DF\u76E1\u76E3\u76E4\u76E7\u76EE"
+                + "\u76F2\u76F4\u76F8\u76FC\u76FE\u7701\u7709\u770B\u771F\u7720"
+                + "\u773C\u773E\u775B\u7761\u7763\u77A7\u77AD\u77DB\u77E3\u77E5"
+                + "\u77ED\u77F3\u7802\u780D\u7814\u7832\u7834\u786C\u788E\u7891"
+                + "\u7897\u789F\u78A7\u78A9\u78B0\u78BA\u78BC\u78C1\u78E8\u78EF"
+                + "\u7901\u790E\u7919\u793A\u793E\u7955\u7956\u795A\u795B\u795D"
+                + "\u795E\u7965\u7968\u797F\u7981\u798D-\u798F\u79AA\u79AE\u79C0\u79C1"
+                + "\u79CB\u79D1\u79D2\u79D8\u79DF\u79E4\u79E6\u79FB\u7A05\u7A0B"
+                + "\u7A0D\u7A2E\u7A31\u7A3F\u7A46\u7A4C\u7A4D\u7A69\u7A76\u7A79"
+                + "\u7A7A\u7A7F\u7A81\u7A97\u7AA9\u7AAE\u7AB6\u7ACB\u7AD9\u7ADF"
+                + "\u7AE0\u7AE5\u7AEF\u7AF6\u7AF9\u7B11\u7B1B\u7B26\u7B28\u7B2C"
+                + "\u7B46\u7B49\u7B4B\u7B54\u7B56\u7B80\u7B97\u7BA1\u7BAD\u7BB1"
+                + "\u7BC0\u7BC4\u7BC7\u7BC9\u7C21\u7C2B\u7C3D\u7C3F\u7C43\u7C4C"
+                + "\u7C4D\u7C64\u7C73\u7C89\u7C97\u7CB5\u7CBE\u7CCA\u7CD5\u7CDF"
+                + "\u7CFB\u7CFE\u7D00\u7D04\u7D05\u7D0D\u7D10\u7D14\u7D19-\u7D1B"
+                + "\u7D20\u7D22\u7D2B\u7D2F\u7D30\u7D39\u7D42\u7D44\u7D50\u7D55"
+                + "\u7D61\u7D66\u7D71\u7D72\u7D93\u7D9C\u7DA0\u7DAD\u7DB1\u7DB2"
+                + "\u7DCA\u7DD2\u7DDA\u7DE3\u7DE8\u7DE9\u7DEC\u7DEF\u7DF4\u7E1B"
+                + "\u7E23\u7E2E\u7E31\u7E3D\u7E3E\u7E41\u7E46\u7E54\u7E5E\u7E6A"
+                + "\u7E73\u7E7C\u7E8C\u7F38\u7F3A\u7F55\u7F6A\u7F6E\u7F70\u7F72"
+                + "\u7F75\u7F77\u7F85\u7F8A\u7F8E\u7F9E\u7FA4\u7FA9\u7FBD\u7FC1"
+                + "\u7FD2\u7FD4\u7FF0\u7FF9\u7FFB\u7FFC\u8000\u8001\u8003\u8005"
+                + "\u800C\u800D\u8010\u8017\u8033\u8036\u804A\u8056\u805A\u805E"
+                + "\u806F\u8070\u8072\u8077\u807D\u8089\u809A\u80A1\u80A5\u80A9"
+                + "\u80AF\u80B2\u80CC\u80CE\u80D6\u80DE\u80E1\u80F8\u80FD\u8106"
+                + "\u812B\u8153\u8154\u8166\u8170\u8173\u817F\u81BD\u81C9\u81D8"
+                + "\u81E3\u81E5\u81E8\u81EA\u81ED\u81F3\u81F4\u81FA\u8207-\u820A"
+                + "\u820C\u820D\u8212\u821E\u821F\u822A\u822C\u8239\u8266\u826F"
+                + "\u8272\u827E\u8292\u829D\u82AC\u82B1\u82B3\u82D7\u82E5\u82E6"
+                + "\u82F1\u8305\u8328\u832B\u8332\u8336\u8349\u8352\u8377\u837C"
+                + "\u8389\u838A\u838E\u83AB\u83DC\u83E9\u83EF\u83F2\u8404\u840A"
+                + "\u842C\u843D\u8449\u8457\u845B\u8461\u8482\u8499\u84B2\u84BC"
+                + "\u84CB\u84EC\u84EE\u8515\u8521\u8523\u856D\u8584\u85A6\u85A9"
+                + "\u85AA\u85C9\u85CD\u85CF\u85DD\u85E4\u85E5\u8606\u8607\u862D"
+                + "\u864E\u8655\u865B\u865F\u8667\u86A9\u86C7\u86CB\u86D9\u8700"
+                + "\u8702\u871C\u8776\u878D\u87A2\u87F2\u87F9\u880D\u883B\u8840"
+                + "\u884C\u8853\u8857\u885B\u885D\u8861\u8863\u8868\u888B\u88AB"
+                + "\u88C1\u88C2\u88D5\u88D8\u88DC\u88DD\u88E1\u88FD\u8907\u8932"
+                + "\u897F\u8981\u8986\u898B\u898F\u8996\u89AA\u89BA\u89BD\u89C0"
+                + "\u89D2\u89E3\u89F8\u8A00\u8A02\u8A08\u8A0A\u8A0E\u8A13\u8A17"
+                + "\u8A18\u8A25\u8A2A\u8A2D\u8A31\u8A34\u8A3B\u8A3C\u8A55\u8A5E"
+                + "\u8A62\u8A66\u8A69\u8A71-\u8A73\u8A87\u8A8C\u8A8D\u8A93\u8A95\u8A9E"
+                + "\u8AA0\u8AA4\u8AAA\u8AB0\u8AB2\u8ABC\u8ABF\u8AC7\u8ACB\u8AD2"
+                + "\u8AD6\u8AF8\u8AFA\u8AFE\u8B00\u8B02\u8B1B\u8B1D\u8B2C\u8B49"
+                + "\u8B58\u8B5C\u8B66\u8B6F\u8B70\u8B77\u8B7D\u8B80\u8B8A\u8B93"
+                + "\u8B9A\u8C37\u8C46\u8C48\u8C50\u8C61\u8C6A\u8C6C\u8C8C\u8C93"
+                + "\u8C9D\u8C9E\u8CA0-\u8CA2\u8CA8\u8CAA-\u8CAC\u8CB4\u8CB7\u8CBB\u8CBC"
+                + "\u8CC0\u8CC7\u8CC8\u8CD3\u8CDC\u8CDE\u8CE2-\u8CE4\u8CE6\u8CEA"
+                + "\u8CED\u8CF4\u8CFA\u8CFC\u8CFD\u8D08\u8D0A\u8D0F\u8D64\u8D6B"
+                + "\u8D70\u8D77\u8D85\u8D8A\u8D95\u8D99\u8DA3\u8DA8\u8DB3\u8DCC"
+                + "\u8DCE\u8DD1\u8DDD\u8DDF\u8DE1\u8DEF\u8DF3\u8E0F\u8E22\u8E5F"
+                + "\u8E64\u8E8D\u8EAB\u8EB2\u8ECA\u8ECC\u8ECD\u8ED2\u8EDF\u8F03"
+                + "\u8F09\u8F14\u8F15\u8F1B\u8F1D\u8F29\u8F2A\u8F2F\u8F38\u8F49"
+                + "\u8F5F\u8F9B\u8FA6\u8FA8\u8FAD\u8FAF-\u8FB2\u8FC5\u8FCE\u8FD1\u8FD4"
+                + "\u8FE6\u8FEA\u8FEB\u8FF0\u8FF4\u8FF7\u8FFD\u9000\u9001\u9003"
+                + "\u9006\u900F\u9010\u9014\u9019-\u901B\u901D\u901F\u9020\u9022"
+                + "\u9023\u9031\u9032\u9038\u903C\u9047\u904A\u904B\u904D\u904E"
+                + "\u9053-\u9055\u9059\u905C\u9060\u9069\u906D\u906E\u9072\u9077"
+                + "\u9078\u907A\u907F-\u9081\u9084\u908A\u908F\u90A3\u90A6\u90AA"
+                + "\u90B1\u90CE\u90E8\u90ED\u90F5\u90FD\u9102\u9109\u912D\u9130"
+                + "\u9149\u914B\u914D\u9152\u9177\u9178\u9189\u9192\u919C\u91AB"
+                + "\u91C7\u91CB-\u91CF\u91D1\u91DD\u91E3\u9234\u9262\u9280\u9285\u9296"
+                + "\u9298\u92B3\u92B7\u92D2\u92FC\u9304\u9322\u9326\u932B\u932F"
+                + "\u934B\u9375\u937E\u938A\u9396\u93AE\u93E1\u9418\u9435\u9451"
+                + "\u9577\u9580\u9583\u9589\u958B\u958F\u9592\u9593\u95A3\u95B1"
+                + "\u95C6\u95CA\u95CD\u95D0\u95DC\u95E1\u9632\u963B\u963F\u9640"
+                + "\u9644\u964D\u9650\u9662-\u9664\u966A\u9670\u9673\u9675-\u9678"
+                + "\u967D\u9686\u968A\u968E\u9694\u969B\u969C\u96A8\u96AA\u96B1"
+                + "\u96B4\u96BB\u96C4-\u96C6\u96C9\u96D6\u96D9\u96DC\u96DE\u96E2"
+                + "\u96E3\u96E8\u96EA\u96F2\u96F6\u96F7\u96FB\u9700\u9707\u970D"
+                + "\u9727\u9732\u9738\u9739\u9742\u9748\u9752\u9756\u975C\u975E"
+                + "\u9760\u9762\u9769\u977C\u978B\u97C3\u97CB\u97D3\u97F3\u97FB"
+                + "\u97FF\u9801\u9802\u9805\u9806\u9808\u9810\u9811\u9813\u9817"
+                + "\u9818\u981E\u982D\u983B\u9846\u984C\u984D\u984F\u9858\u985E"
+                + "\u9867\u986F\u98A8\u98C4\u98DB\u98DF\u98EF\u98F2\u98FD\u98FE"
+                + "\u9905\u990A\u9910\u9918\u9928\u9996\u9999\u99AC\u99D0\u99D5"
+                + "\u99DB\u9A0E\u9A19\u9A37\u9A45\u9A57\u9A5A\u9AA8\u9AD4\u9AD8"
+                + "\u9AEE\u9B06\u9B25\u9B27\u9B31\u9B3C\u9B41\u9B42\u9B45\u9B54"
+                + "\u9B5A\u9B6F\u9BAE\u9CE5\u9CF3\u9CF4\u9D3B\u9D5D\u9DF9\u9E7F"
+                + "\u9E97\u9EA5\u9EB5\u9EBB\u9EBC\u9EC3\u9ECE\u9ED1\u9ED8\u9EDE"
+                + "\u9EE8\u9F13\u9F20\u9F3B\u9F4A\u9F4B\u9F52\u9F61\u9F8D\u9F9C"
+                + "\uFE30-\uFE44\uFE49-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B"
+                + "\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20"
+                + "\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D]"));
+    }
+
+    private String dropRegion(String localeName) {
+        return new Locale.Builder()
+                         .setLanguageTag(localeName)
+                         .setRegion("")
+                         .build()
+                         .toLanguageTag();
+    }
+
+    private UnicodeSet requiredExtraChars(String localeName) {
+        if (EXEMPLAR_MAP.containsKey(localeName)) {
+            return EXEMPLAR_MAP.get(localeName);
+        }
+        // Drop the region code.
+        final String parentLocale = dropRegion(localeName);
+        if (EXEMPLAR_MAP.containsKey(parentLocale)) {
+            return EXEMPLAR_MAP.get(parentLocale);
+        }
+
+        // Unknown locale. Return an empty set.
+        return UnicodeSet.EMPTY;
+    }
+
+    @Test
+    public void testCoverage() {
+        final Paint paint = new Paint();
+        final String[] localeNames = Resources.getSystem().getStringArray(
+                Resources.getSystem().getIdentifier("supported_locales", "array", "android"));
+
+        final UnicodeSet allRequired = new UnicodeSet(MIN_COVERAGE);
+        // Add all characters needed for supported locales.
+        for (String localeName : localeNames) {
+            allRequired.addAll(requiredExtraChars(localeName));
+        }
+        for (String str : allRequired) {
+            str.codePoints().filter(cp -> {
+                // No need to check for format or control characters. They are handled by
+                // HarfBuzz and other parts of the rendering system.
+                final int gc = UCharacter.getType(cp);
+                return (gc != UCharacterCategory.CONTROL && gc != UCharacter.FORMAT);
+            }).forEach(cp -> {
+                final String characterAsString = new String(Character.toChars(cp));
+                assertTrue(
+                        String.format("No glyph for U+%04X", cp),
+                        paint.hasGlyph(characterAsString));
+            });
+        }
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java b/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
index e8e0eb8..4d25740 100644
--- a/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
@@ -28,14 +28,12 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.app.Instrumentation;
-import android.app.UiAutomation;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.TextUtils;
-import android.view.accessibility.AccessibilityEvent;
 import android.widget.NumberPicker;
 
 import com.android.compatibility.common.util.CtsTouchUtils;
@@ -52,10 +50,8 @@
     private static final String[] NUMBER_NAMES3 = {"One", "Two", "Three"};
     private static final String[] NUMBER_NAMES_ALT3 = {"Three", "Four", "Five"};
     private static final String[] NUMBER_NAMES5 = {"One", "Two", "Three", "Four", "Five"};
-    private static final long TIMEOUT_ACCESSIBILITY_EVENT = 5 * 1000;
 
     private Instrumentation mInstrumentation;
-    private UiAutomation mUiAutomation;
     private NumberPickerCtsActivity mActivity;
     private NumberPicker mNumberPicker;
 
@@ -66,7 +62,6 @@
     @Before
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mUiAutomation = mInstrumentation.getUiAutomation();
         mActivity = mActivityRule.getActivity();
         mNumberPicker = (NumberPicker) mActivity.findViewById(R.id.number_picker);
     }
@@ -265,41 +260,32 @@
                 mNumberPicker.getDisplayedValueForCurrentSelection()));
     }
 
+    @UiThreadTest
     @Test
-    public void testAccessValue() throws Throwable {
+    public void testAccessValue() {
+        mNumberPicker.setMinValue(20);
+        mNumberPicker.setMaxValue(22);
+        mNumberPicker.setDisplayedValues(NUMBER_NAMES3);
+
         final NumberPicker.OnValueChangeListener mockValueChangeListener =
                 mock(NumberPicker.OnValueChangeListener.class);
+        mNumberPicker.setOnValueChangedListener(mockValueChangeListener);
 
-        mInstrumentation.runOnMainSync(() -> {
-            mNumberPicker.setMinValue(20);
-            mNumberPicker.setMaxValue(22);
-            mNumberPicker.setDisplayedValues(NUMBER_NAMES3);
+        mNumberPicker.setValue(21);
+        assertEquals(21, mNumberPicker.getValue());
 
-            mNumberPicker.setOnValueChangedListener(mockValueChangeListener);
-        });
+        mNumberPicker.setValue(20);
+        assertEquals(20, mNumberPicker.getValue());
 
-        mUiAutomation.executeAndWaitForEvent(() ->
-                    mInstrumentation.runOnMainSync(() -> mNumberPicker.setValue(21)),
-                (AccessibilityEvent event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
-                TIMEOUT_ACCESSIBILITY_EVENT);
+        mNumberPicker.setValue(22);
+        assertEquals(22, mNumberPicker.getValue());
 
-        mInstrumentation.runOnMainSync(() -> {
-            assertEquals(21, mNumberPicker.getValue());
+        // Check trying to set value out of min/max range
+        mNumberPicker.setValue(10);
+        assertEquals(20, mNumberPicker.getValue());
 
-            mNumberPicker.setValue(20);
-            assertEquals(20, mNumberPicker.getValue());
-
-            mNumberPicker.setValue(22);
-            assertEquals(22, mNumberPicker.getValue());
-
-            // Check trying to set value out of min/max range
-            mNumberPicker.setValue(10);
-            assertEquals(20, mNumberPicker.getValue());
-
-            mNumberPicker.setValue(100);
-            assertEquals(22, mNumberPicker.getValue());
-        });
+        mNumberPicker.setValue(100);
+        assertEquals(22, mNumberPicker.getValue());
 
         // Since all changes to value are via API calls, we should have no interactions /
         // callbacks on our listener.
@@ -376,15 +362,11 @@
         final int[] numberPickerLocationOnScreen = new int[2];
         mNumberPicker.getLocationOnScreen(numberPickerLocationOnScreen);
 
-        mUiAutomation.executeAndWaitForEvent(() ->
-                CtsTouchUtils.emulateDragGesture(mInstrumentation,
-                        numberPickerLocationOnScreen[0] + mNumberPicker.getWidth() / 2,
-                        numberPickerLocationOnScreen[1] + mNumberPicker.getHeight() - 1,
-                        0,
-                        -(mNumberPicker.getHeight() - 2)),
-                (AccessibilityEvent event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED,
-                TIMEOUT_ACCESSIBILITY_EVENT);
+        CtsTouchUtils.emulateDragGesture(mInstrumentation,
+                numberPickerLocationOnScreen[0] + mNumberPicker.getWidth() / 2,
+                numberPickerLocationOnScreen[1] + mNumberPicker.getHeight() - 1,
+                0,
+                - (mNumberPicker.getHeight() - 2));
 
         // At this point we expect that the drag-up gesture has selected the value
         // that was "below" the previously selected one, and that our value change listener