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