Merge "Hdmi Cec Remote Control Interrupted Press and Hold"
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index fb753b3..48ebced 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -299,12 +299,33 @@
# do captures on 2 cameras
caps = {}
for i, fmt in enumerate(fmts):
+ physicalSizes = {}
+
capture_cam_ids = physical_ids
if fmt == 'raw':
capture_cam_ids = physical_raw_ids
+
+ for physical_id in capture_cam_ids:
+ configs = props_physical[physical_id]['android.scaler.streamConfigurationMap']\
+ ['availableStreamConfigurations']
+ if fmt == 'raw':
+ fmt_codes = 0x20
+ fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes]
+ else:
+ fmt_codes = 0x23
+ fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes]
+
+ out_configs = [cfg for cfg in fmt_configs if cfg['input'] is False]
+ out_sizes = [(cfg['width'], cfg['height']) for cfg in out_configs]
+ physicalSizes[physical_id] = max(out_sizes, key=lambda item: item[1])
+
out_surfaces = [{'format': 'yuv', 'width': w, 'height': h},
- {'format': fmt, 'physicalCamera': capture_cam_ids[0]},
- {'format': fmt, 'physicalCamera': capture_cam_ids[1]}]
+ {'format': fmt, 'physicalCamera': capture_cam_ids[0],
+ 'width': physicalSizes[capture_cam_ids[0]][0],
+ 'height': physicalSizes[capture_cam_ids[0]][1]},
+ {'format': fmt, 'physicalCamera': capture_cam_ids[1],
+ 'width': physicalSizes[capture_cam_ids[1]][0],
+ 'height': physicalSizes[capture_cam_ids[1]][1]},]
out_surfaces_supported = cam.is_stream_combination_supported(out_surfaces)
its.caps.skip_unless(out_surfaces_supported)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
index 126611f..bae8e81 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
@@ -26,14 +26,20 @@
import com.android.cts.verifier.R;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.concurrent.Executor;
+import javax.crypto.Cipher;
+
/**
* Test for {@link BiometricPrompt}. This test runs twice, once with confirmation required,
* once without. Both tests use crypto objects.
*/
public class BiometricPromptBoundKeysTest extends FingerprintBoundKeysTest {
+ private static final String TAG = "BiometricPromptBoundKeysTest";
+
private static final int STATE_TEST_REQUIRE_CONFIRMATION = 1; // confirmation required
private static final int STATE_TEST_NO_CONFIRMATION = 2; // no confirmation required
@@ -79,18 +85,84 @@
}
@Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
protected void onPermissionsGranted() {
+ Button startTestButton = findViewById(R.id.sec_start_test_button);
mBiometricManager = getSystemService(BiometricManager.class);
final int result = mBiometricManager.canAuthenticate();
- if (result == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
+ if (result == BiometricManager.BIOMETRIC_SUCCESS) {
+
+ // The Android 10 CDD allows for integration of Strong, Weak, and Convenience
+ // biometrics on the device. A subset (Strong, Weak) are allowed for application
+ // integration (e.g. with the public APIs). However, the Android 10 API surface is not
+ // wide enough to support this.
+ //
+ // Although this was addressed in Android 11 with Authenticators.BIOMETRIC_STRONG,
+ // Authenticators.BIOMETRIC_WEAK, and the canAuthenticate(int) APIs etc, we still need
+ // to patch the hole in Android 10. The POR is as follows.
+ //
+ // Devices with weak biometrics should implement a @hide API with the following
+ // signature: `int BiometricManager#canAuthenticate(BiometricPrompt.CryptoObject)`.
+ // On devices with weak-only, the method should return BIOMETRIC_ERROR_NO_HARDWARE. The
+ // CryptoObject does not need to be initialized/valid, and is used only to find the
+ // method signature. (Yes, in hindsight, we should have settled on the @hide API being
+ // more like `int BiometricManager#canAuthenticateWithCrypto()`
+ //
+ // The androidx.biometric library can then bring Android11-like functionality to
+ // developers by similarly checking for the existence/result of this @hide API.
+
+ try {
+ final Method canAuthenticateCrypto = BiometricManager.class.getMethod(
+ "canAuthenticate",
+ BiometricPrompt.CryptoObject.class);
+ final Object canAuthenticateResult = canAuthenticateCrypto
+ .invoke(mBiometricManager, (Cipher) null);
+
+ if (canAuthenticateResult instanceof Integer) {
+ switch ((int) canAuthenticateResult) {
+ case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
+ // Device does not have a strong authenticator, but has a weak one.
+ // Allow the test to pass.
+ startTestButton.setEnabled(false);
+ getPassButton().setEnabled(true);
+
+ showToast("The device has WEAK biometrics but no STRONG biometrics."
+ + " You can skip the keystore integration test.");
+ break;
+ case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
+ // HAL/Hardware has an issue. This is definitely not expected. In this
+ // case, we should assume it should be in the enrolled/available state.
+ // Test for Keystore integration in this case.
+ case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
+ // The user can be asked to enroll. Test for Keystore integration in
+ // this case.
+ case BiometricManager.BIOMETRIC_SUCCESS:
+ // Device reports that a Strong authenticator is available and enrolled.
+ // Test for Keystore integration in this case.
+ default:
+ showToast("canAuthenticateCrypto result: " + canAuthenticateResult
+ + " Please proceed with the keystore integration test");
+ break;
+ }
+ }
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ // Proceed with the keystore integration test as usual.
+ }
+
+
+ } else if (result == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
showToast("No biometric features, test passed.");
getPassButton().setEnabled(true);
} else if (result == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
showToast("Biometric unavailable, something is wrong with your device");
} else if (result == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
showToast("Error: " + result + " Please ensure you have a biometric enrolled");
- Button startTestButton = findViewById(R.id.sec_start_test_button);
+
startTestButton.setEnabled(false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
index b804b45..c111d30 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
@@ -83,6 +83,10 @@
return R.string.sec_fingerprint_bound_key_test_info;
}
+ protected String getTag() {
+ return TAG;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -269,6 +273,7 @@
protected void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+ Log.d(getTag(), message);
}
public static class FingerprintAuthDialogFragment extends DialogFragment {
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
index 16f7610..9a851f0 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
@@ -15,13 +15,18 @@
*/
package com.android.compatibility.common.deviceinfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.os.Bundle;
-
-import com.android.compatibility.common.deviceinfo.DeviceInfo;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.os.Process;
import com.android.compatibility.common.util.DeviceInfoStore;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* PackageDeviceInfo collector.
*/
@@ -35,24 +40,116 @@
private static final String MIN_SDK = "min_sdk";
private static final String TARGET_SDK = "target_sdk";
+ private static final String REQUESTED_PERMISSIONS = "requested_permissions";
+ private static final String PERMISSION_NAME = "name";
+ private static final String PERMISSION_FLAGS = "flags";
+ private static final String PERMISSION_GROUP = "permission_group";
+ private static final String PERMISSION_PROTECTION = "protection_level";
+ private static final String PERMISSION_PROTECTION_FLAGS = "protection_level_flags";
+
+ private static final String HAS_SYSTEM_UID = "has_system_uid";
+
+ private static final String SHARES_INSTALL_PERMISSION = "shares_install_packages_permission";
+ private static final String INSTALL_PACKAGES_PERMISSION = "android.permission.INSTALL_PACKAGES";
+
+
@Override
protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
- PackageManager pm = getContext().getPackageManager();
+ final PackageManager pm = getContext().getPackageManager();
+
+ final List<PackageInfo> allPackages =
+ pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
+
store.startArray(PACKAGE);
- for (PackageInfo pkg : pm.getInstalledPackages(0)) {
+ for (PackageInfo pkg : allPackages) {
store.startGroup();
store.addResult(NAME, pkg.packageName);
store.addResult(VERSION_NAME, pkg.versionName);
- if (pkg.applicationInfo != null) {
- String dir = pkg.applicationInfo.sourceDir;
+ store.startArray(REQUESTED_PERMISSIONS);
+ if (pkg.requestedPermissions != null && pkg.requestedPermissions.length > 0) {
+ for (String permission : pkg.requestedPermissions) {
+ try {
+ final PermissionInfo pi = pm.getPermissionInfo(permission, 0);
+
+ store.startGroup();
+ store.addResult(PERMISSION_NAME, permission);
+ writePermissionsDetails(pi, store);
+ store.endGroup();
+ } catch (PackageManager.NameNotFoundException e) {
+ // ignore unrecognized permission and continue
+ }
+ }
+ }
+ store.endArray();
+
+ final ApplicationInfo appInfo = pkg.applicationInfo;
+ if (appInfo != null) {
+ String dir = appInfo.sourceDir;
store.addResult(SYSTEM_PRIV, dir != null && dir.startsWith(PRIV_APP_DIR));
- store.addResult(MIN_SDK, pkg.applicationInfo.minSdkVersion);
- store.addResult(TARGET_SDK, pkg.applicationInfo.targetSdkVersion);
+ store.addResult(MIN_SDK, appInfo.minSdkVersion);
+ store.addResult(TARGET_SDK, appInfo.targetSdkVersion);
+
+ store.addResult(HAS_SYSTEM_UID, appInfo.uid < Process.FIRST_APPLICATION_UID);
+
+ final boolean canInstall = sharesUidWithInstallerPackage(pm, appInfo.uid);
+ store.addResult(SHARES_INSTALL_PERMISSION, canInstall);
}
store.endGroup();
}
- store.endArray(); // Package
+ store.endArray(); // "package"
+ }
+
+ private static boolean sharesUidWithInstallerPackage(PackageManager pm, int uid) {
+ final String[] sharesUidWith = pm.getPackagesForUid(uid);
+
+ if (sharesUidWith == null) {
+ return false;
+ }
+
+ // Approx 20 permissions per package for rough estimate of sizing
+ final int capacity = sharesUidWith.length * 20;
+ final List<String> sharedPermissions = new ArrayList<>(capacity);
+ for (String pkg :sharesUidWith){
+ try {
+ final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
+
+ if (info.requestedPermissions == null) {
+ continue;
+ }
+
+ for (String p : info.requestedPermissions) {
+ if (p != null) {
+ sharedPermissions.add(p);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // ignore, continue
+ }
+ }
+
+ return sharedPermissions.contains(PackageDeviceInfo.INSTALL_PACKAGES_PERMISSION);
+ }
+
+ private static void writePermissionsDetails(PermissionInfo pi, DeviceInfoStore store)
+ throws IOException {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ store.addResult(PERMISSION_FLAGS, pi.flags);
+ } else {
+ store.addResult(PERMISSION_FLAGS, 0);
+ }
+
+ store.addResult(PERMISSION_GROUP, pi.group);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ store.addResult(PERMISSION_PROTECTION, pi.getProtection());
+ store.addResult(PERMISSION_PROTECTION_FLAGS, pi.getProtectionFlags());
+ } else {
+ store.addResult(PERMISSION_PROTECTION,
+ pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE);
+ store.addResult(PERMISSION_PROTECTION_FLAGS,
+ pi.protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE);
+ }
}
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java
index 32dedea..6cefb5d 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java
@@ -18,6 +18,8 @@
import android.util.Log;
+import androidx.annotation.Nullable;
+
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -29,6 +31,7 @@
private static final String TAG = "RetryRule";
private final int mMaxAttempts;
+ private final Runnable mCleaner;
/**
* Retries the underlying test when it catches a {@link RetryableException}.
@@ -38,10 +41,24 @@
* @throws IllegalArgumentException if {@code retries} is less than {@code 0}.
*/
public RetryRule(int retries) {
+ this(retries, null);
+ }
+
+ /**
+ * Retries the underlying test when it catches a {@link RetryableException}.
+ *
+ * @param retries number of retries. Use {@code 0} to disable rule.
+ * @param cleaner a {@link Runnable} to clean up the objects generated by the testing
+ * between retries
+ *
+ * @throws IllegalArgumentException if {@code retries} is less than {@code 0}.
+ */
+ public RetryRule(int retries, @Nullable Runnable cleaner) {
if (retries < 0) {
throw new IllegalArgumentException("retries must be more than 0");
}
mMaxAttempts = retries + 1;
+ mCleaner = cleaner;
}
@Override
@@ -78,6 +95,9 @@
+ " to " + timeout.ms() + "ms");
}
caught = e;
+ if (i != mMaxAttempts && mCleaner != null) {
+ mCleaner.run();
+ }
}
Log.w(TAG, "Arrrr! " + name + " failed at attempt " + i + "/" + mMaxAttempts
+ ": " + caught);
diff --git a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
index 644d95f..571a1b8 100644
--- a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
+++ b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
@@ -19,11 +19,15 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.expectThrows;
+import androidx.annotation.Nullable;
+
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
@@ -43,15 +47,22 @@
private final int mNumberFailures;
private int mNumberCalls;
private final T mException;
+ private final Runnable mCleaner;
RetryableStatement(int numberFailures, T exception) {
+ this(numberFailures, exception, null);
+ }
+
+ RetryableStatement(int numberFailures, T exception, @Nullable Runnable cleaner) {
mNumberFailures = numberFailures;
mException = exception;
+ mCleaner = cleaner;
}
@Override
public void evaluate() throws Throwable {
mNumberCalls++;
+ verifyCleaner();
if (mNumberCalls <= mNumberFailures) {
throw mException;
}
@@ -61,6 +72,13 @@
public String toString() {
return "RetryableStatement: failures=" + mNumberFailures + ", calls=" + mNumberCalls;
}
+
+ private void verifyCleaner() {
+ if (mCleaner == null) {
+ return;
+ }
+ verify(mCleaner, times(mNumberCalls - 1)).run();
+ }
}
private @Mock Statement mMockStatement;
@@ -68,6 +86,8 @@
@Test
public void testInvalidConstructor() throws Throwable {
assertThrows(IllegalArgumentException.class, () -> new RetryRule(-1));
+ assertThrows(IllegalArgumentException.class, () -> new RetryRule(-1, null));
+ assertThrows(IllegalArgumentException.class, () -> new RetryRule(-1, () -> {}));
}
@Test
@@ -78,6 +98,57 @@
}
@Test
+ public void testDoCleanOnRetryableException() throws Throwable {
+ final Runnable cleaner = mock(Runnable.class);
+ final RetryRule rule = new RetryRule(2, cleaner);
+
+ rule.apply(new RetryableStatement<RetryableException>(2, sRetryableException, cleaner),
+ mDescription).evaluate();
+
+ verify(cleaner, times(2)).run();
+ }
+
+ @Test
+ public void testKeepLastStatusWhenFailOnRetryableException() throws Throwable {
+ final Runnable cleaner = mock(Runnable.class);
+ final RetryRule rule = new RetryRule(2, cleaner);
+
+ final RetryableException actualException = expectThrows(RetryableException.class,
+ () -> rule.apply(
+ new RetryableStatement<RetryableException>(3, sRetryableException, cleaner),
+ mDescription).evaluate());
+
+ assertThat(actualException).isSameAs(sRetryableException);
+ verify(cleaner, times(2)).run();
+ }
+
+ @Test
+ public void testNeverCleanWhenStatementPass() throws Throwable {
+ final Runnable cleaner = mock(Runnable.class);
+ final RetryRule rule = new RetryRule(2, cleaner);
+
+ rule.apply(mMockStatement, mDescription).evaluate();
+
+ verify(mMockStatement, times(1)).evaluate();
+ verify(cleaner, never()).run();
+ }
+
+ @Test
+ public void testNeverCleanWhenDisabledAndStatementThrowsRetryableException() throws Throwable {
+ final RetryableException exception = new RetryableException("Y U NO?");
+ final Runnable cleaner = mock(Runnable.class);
+ final RetryRule rule = new RetryRule(0, cleaner);
+ doThrow(exception).when(mMockStatement).evaluate();
+
+ final RetryableException actualException = expectThrows(RetryableException.class,
+ () -> rule.apply(mMockStatement, mDescription).evaluate());
+
+ assertThat(actualException).isSameAs(exception);
+ verify(mMockStatement, times(1)).evaluate();
+ verify(cleaner, never()).run();
+ }
+
+ @Test
public void testPassOnRetryableExceptionWithTimeout() throws Throwable {
final Timeout timeout = new Timeout("YOUR TIME IS GONE", 1, 2, 10);
final RetryableException exception = new RetryableException(timeout, "Y U NO?");
diff --git a/hostsidetests/apex/src/android/apex/cts/ApexTest.java b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
index 0347299..9bb09d8 100644
--- a/hostsidetests/apex/src/android/apex/cts/ApexTest.java
+++ b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
@@ -33,7 +33,7 @@
}
private boolean isGSI() throws Exception {
- String systemProduct = getDevice().getProperty("ro.product.system.name");
+ String systemProduct = getDevice().getProperty("ro.product.system_ext.name");
return systemProduct.equals("aosp_arm")
|| systemProduct.equals("aosp_arm64")
|| systemProduct.equals("aosp_x86")
diff --git a/hostsidetests/appcompat/OWNERS b/hostsidetests/appcompat/OWNERS
new file mode 100644
index 0000000..4e3ec29
--- /dev/null
+++ b/hostsidetests/appcompat/OWNERS
@@ -0,0 +1,9 @@
+# Bug component: 610774
+
+# Use this reviewer by default.
+platform-compat-eng+reviews@google.com
+
+andreionea@google.com
+atrost@google.com
+mathewi@google.com
+satayev@google.com
\ No newline at end of file
diff --git a/hostsidetests/appcompat/compatchanges/Android.bp b/hostsidetests/appcompat/compatchanges/Android.bp
new file mode 100644
index 0000000..ca0867b
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2020 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.
+
+java_test_host {
+ name: "CtsAppCompatHostTestCases",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "guava",
+ ],
+ static_libs:["CompatChangeGatingTestBase"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/appcompat/compatchanges/AndroidTest.xml b/hostsidetests/appcompat/compatchanges/AndroidTest.xml
new file mode 100644
index 0000000..94f82cc
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 appcompat host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsAppCompatHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/appcompat/compatchanges/app/Android.bp b/hostsidetests/appcompat/compatchanges/app/Android.bp
new file mode 100644
index 0000000..9ff5b17
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/app/Android.bp
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2020 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.
+//
+
+android_test_helper_app {
+ name: "CtsHostsideCompatChangeTestsApp",
+ defaults: ["cts_support_defaults"],
+ platform_apis: true,
+ srcs: ["src/**/*.java"],
+ libs: [
+ "junit",
+ ],
+ static_libs: [
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ ],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml b/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
new file mode 100644
index 0000000..28b33d2
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.appcompat">
+ <!-- targetSdkVersion for this test must be below 1234 -->
+ <uses-sdk android:targetSdkVersion="29"/>
+ <application
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.appcompat" />
+
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java
new file mode 100644
index 0000000..7025a96
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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.appcompat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.compat.CompatChanges;
+import android.content.Context;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
+ *
+ * These test methods have additional setup and post run checks done host side by
+ * {@link com.android.cts.appcompat.CompatChangesSystemApiTest}.
+ *
+ * The setup adds an override for the change id being tested, and the post run step checks if
+ * that change id has been logged to statsd.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class CompatChangesTest {
+ private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
+ @Before
+ public void setUp() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+ }
+
+ @After
+ public void tearDown() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
+ @Test
+ public void isChangeEnabled_changeEnabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
+ @Test
+ public void isChangeEnabledPackageName_changeEnabled() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+ context.getUser())).isTrue();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
+ @Test
+ public void isChangeEnabledUid_changeEnabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
+ @Test
+ public void isChangeEnabled_changeDisabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
+ @Test
+ public void isChangeEnabledPackageName_changeDisabled() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+ context.getUser())).isFalse();
+ }
+
+ /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
+ @Test
+ public void isChangeEnabledUid_changeDisabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
+ }
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
new file mode 100644
index 0000000..699af1a
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 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.appcompat;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
+ */
+
+public class CompatChangesSystemApiTest extends CompatChangeGatingTestCase {
+
+ protected static final String TEST_APK = "CtsHostsideCompatChangeTestsApp.apk";
+ protected static final String TEST_PKG = "com.android.cts.appcompat";
+
+ private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
+
+ @Override
+ protected void setUp() throws Exception {
+ installPackage(TEST_APK, true);
+ }
+
+ public void testIsChangeEnabled() throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".CompatChangesTest", "isChangeEnabled_changeEnabled",
+ /*enabledChanges*/ImmutableSet.of(CTS_SYSTEM_API_CHANGEID),
+ /*disabledChanges*/ ImmutableSet.of());
+ }
+
+ public void testIsChangeEnabledPackageName() throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".CompatChangesTest",
+ "isChangeEnabledPackageName_changeEnabled",
+ /*enabledChanges*/ImmutableSet.of(CTS_SYSTEM_API_CHANGEID),
+ /*disabledChanges*/ ImmutableSet.of());
+ }
+
+ public void testIsChangeEnabledUid() throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".CompatChangesTest", "isChangeEnabledUid_changeEnabled",
+ /*enabledChanges*/ImmutableSet.of(CTS_SYSTEM_API_CHANGEID),
+ /*disabledChanges*/ ImmutableSet.of());
+ }
+
+ public void testIsChangeDisabled() throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".CompatChangesTest", "isChangeEnabled_changeDisabled",
+ /*enabledChanges*/ImmutableSet.of(),
+ /*disabledChanges*/ ImmutableSet.of(CTS_SYSTEM_API_CHANGEID));
+ }
+
+ public void testIsChangeDisabledPackageName() throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".CompatChangesTest",
+ "isChangeEnabledPackageName_changeDisabled",
+ /*enabledChanges*/ImmutableSet.of(),
+ /*disabledChanges*/ ImmutableSet.of(CTS_SYSTEM_API_CHANGEID));
+ }
+
+ public void testIsChangeDisabledUid() throws Exception {
+ runDeviceCompatTest(TEST_PKG, ".CompatChangesTest", "isChangeEnabledUid_changeDisabled",
+ /*enabledChanges*/ImmutableSet.of(),
+ /*disabledChanges*/ ImmutableSet.of(CTS_SYSTEM_API_CHANGEID));
+ }
+}
diff --git a/hostsidetests/appsecurity/res/apexsigverify/OWNERS b/hostsidetests/appsecurity/res/apexsigverify/OWNERS
new file mode 100644
index 0000000..18ae217
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 494373
+dariofreni@google.com
diff --git a/hostsidetests/appsecurity/res/apexsigverify/README.md b/hostsidetests/appsecurity/res/apexsigverify/README.md
index c3842e1..34d9ba6 100644
--- a/hostsidetests/appsecurity/res/apexsigverify/README.md
+++ b/hostsidetests/appsecurity/res/apexsigverify/README.md
@@ -2,10 +2,20 @@
## AOSP Well known Key source path
+art/build/apex/com.android.art.avbpubkey
art/build/apex/com.android.runtime.avbpubkey
+packages/services/Telephony/apex/com.android.telephony.avbpubkey
+packages/modules/Cronet/apex/com.android.cronet.avbpubkey
+packages/modules/DnsResolver/apex/com.android.resolv.avbpubkey
external/conscrypt/apex/com.android.conscrypt.avbpubkey
+libcore/apex/com.android.i18n.avbpubkey
+bionic/apex/com.android.runtime.avbpubkey
frameworks/av/apex/com.android.media.swcodec.avbpubkey
frameworks/av/apex/com.android.media.avbpubkey
+frameworks/base/packages/Tethering/apex/com.android.tethering.avbpubkey
+frameworks/base/apex/sdkextensions/com.android.sdkext.avbpubkey
+frameworks/ml/nn/apex/com.android.neuralnetworks.avbpubkey
+frameworks/opt/net/ike/apex/com.android.ipsec.avbpubkey
system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.avbpubkey
system/apex/apexd/apexd_testdata/com.android.apex.test_package.no_inst_key.avbpubkey
system/apex/apexd/apexd_testdata/com.android.apex.test_package.prepostinstall.fail.avbpubkey
@@ -15,10 +25,13 @@
system/apex/tests/testdata/com.android.apex.test.avbpubkey
system/apex/apexer/testdata/com.android.example.apex.avbpubkey
system/apex/apexer/etc/com.android.support.apexer.avbpubkey
-system/timezone/apex/com.android.tzdata.avbpubkey
system/netd/apex/com.android.resolv.avbpubkey
+system/timezone/apex/com.android.tzdata.avbpubkey
+system/bt/apex/com.android.bluetooth.updatable.avbpubkey
+system/core/adb/apex/com.android.adbd.avbpubkey
system/core/rootdir/avb/s-gsi.avbpubkey
+system/core/rootdir/avb/q-developer-gsi.avbpubkey
system/core/rootdir/avb/q-gsi.avbpubkey
system/core/rootdir/avb/r-gsi.avbpubkey
-## Updating public keys
\ No newline at end of file
+## Updating public keys
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.adbd.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.adbd.avbpubkey
new file mode 100644
index 0000000..06235bd
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.adbd.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.art.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.art.avbpubkey
new file mode 100644
index 0000000..0d9cb49
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.art.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.bluetooth.updatable.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.bluetooth.updatable.avbpubkey
new file mode 100644
index 0000000..969211f
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.bluetooth.updatable.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.cronet.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.cronet.avbpubkey
new file mode 100644
index 0000000..38aebe0
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.cronet.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.i18n.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.i18n.avbpubkey
new file mode 100644
index 0000000..f1c97a0
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.i18n.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.ipsec.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.ipsec.avbpubkey
new file mode 100644
index 0000000..a6b43c8
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.ipsec.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.neuralnetworks.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.neuralnetworks.avbpubkey
new file mode 100644
index 0000000..3c96cd1
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.neuralnetworks.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.sdkext.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.sdkext.avbpubkey
new file mode 100644
index 0000000..8f47741
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.sdkext.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.telephony.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.telephony.avbpubkey
new file mode 100644
index 0000000..cba14427
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.telephony.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.tethering.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.tethering.avbpubkey
new file mode 100644
index 0000000..9a2c017
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.tethering.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/q-developer-gsi.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/q-developer-gsi.avbpubkey
new file mode 100644
index 0000000..0ace69d
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/q-developer-gsi.avbpubkey
Binary files differ
diff --git a/hostsidetests/hdmicec/OWNERS b/hostsidetests/hdmicec/OWNERS
index 254c58d..e7f5c32 100644
--- a/hostsidetests/hdmicec/OWNERS
+++ b/hostsidetests/hdmicec/OWNERS
@@ -1,3 +1,3 @@
-# Bug component: 141606867
+# Bug component: 114946
nchalko@google.com
amyjojo@google.com
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecDevice.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecDevice.java
index 3dac5d5..68c270a 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecDevice.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecDevice.java
@@ -21,12 +21,20 @@
public enum CecDevice {
TV(0x0),
- RECORDING_1(0x1),
+ RECORDER_1(0x1),
+ RECORDER_2(0x2),
TUNER_1(0x3),
PLAYBACK_1(0x4),
AUDIO_SYSTEM(0x5),
+ TUNER_2(0x6),
+ TUNER_3(0x7),
PLAYBACK_2(0x8),
+ RECORDER_3(0x9),
+ TUNER_4(0xa),
PLAYBACK_3(0xb),
+ RESERVED_1(0xc),
+ RESERVED_2(0xd),
+ SPECIFIC_USE(0xe),
BROADCAST(0xf);
private final int playerId;
@@ -53,8 +61,15 @@
return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_TV);
case AUDIO_SYSTEM:
return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM);
- case RECORDING_1:
- return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_RECORDING_DEVICE);
+ case RECORDER_1:
+ case RECORDER_2:
+ case RECORDER_3:
+ return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_RECORDER);
+ case TUNER_1:
+ case TUNER_2:
+ case TUNER_3:
+ case TUNER_4:
+ return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_TUNER);
default:
return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_RESERVED);
}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
index 6383a4a..0565eae 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
@@ -41,7 +41,7 @@
public static final int UNRECOGNIZED_OPCODE = 0x0;
public static final int CEC_DEVICE_TYPE_TV = 0;
- public static final int CEC_DEVICE_TYPE_RECORDING_DEVICE = 1;
+ public static final int CEC_DEVICE_TYPE_RECORDER = 1;
public static final int CEC_DEVICE_TYPE_RESERVED = 2;
public static final int CEC_DEVICE_TYPE_TUNER = 3;
public static final int CEC_DEVICE_TYPE_PLAYBACK_DEVICE = 4;
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
index e5ab1d9..c6df9f7 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
@@ -172,7 +172,7 @@
final String originalLanguage = extractLanguage(locale);
final String language = originalLanguage.equals("spa") ? "eng" : "spa";
try {
- hdmiCecClient.sendCecMessage(CecDevice.RECORDING_1, CecDevice.BROADCAST,
+ hdmiCecClient.sendCecMessage(CecDevice.RECORDER_1, CecDevice.BROADCAST,
CecMessage.SET_MENU_LANGUAGE, hdmiCecClient.convertStringToHexParams(language));
assertEquals(originalLanguage, extractLanguage(getSystemLocale()));
} finally {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
index 3f51843..1aa8fe4 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
@@ -87,7 +87,7 @@
checkDeviceAsleepAfterStandbySent(CecDevice.TV, CecDevice.BROADCAST);
/* Wake up the TV */
hdmiCecClient.sendConsoleMessage("on " + CecDevice.TV);
- checkDeviceAsleepAfterStandbySent(CecDevice.RECORDING_1, CecDevice.BROADCAST);
+ checkDeviceAsleepAfterStandbySent(CecDevice.RECORDER_1, CecDevice.BROADCAST);
/* Wake up the TV */
hdmiCecClient.sendConsoleMessage("on " + CecDevice.TV);
checkDeviceAsleepAfterStandbySent(CecDevice.AUDIO_SYSTEM, CecDevice.BROADCAST);
@@ -109,7 +109,7 @@
getDevice().executeShellCommand("reboot");
getDevice().waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
checkDeviceAsleepAfterStandbySent(CecDevice.TV, CecDevice.PLAYBACK_1);
- checkDeviceAsleepAfterStandbySent(CecDevice.RECORDING_1, CecDevice.PLAYBACK_1);
+ checkDeviceAsleepAfterStandbySent(CecDevice.RECORDER_1, CecDevice.PLAYBACK_1);
checkDeviceAsleepAfterStandbySent(CecDevice.AUDIO_SYSTEM, CecDevice.PLAYBACK_1);
checkDeviceAsleepAfterStandbySent(CecDevice.PLAYBACK_2, CecDevice.PLAYBACK_1);
checkDeviceAsleepAfterStandbySent(CecDevice.BROADCAST, CecDevice.PLAYBACK_1);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
index 6369470..bca2d47 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
@@ -122,67 +122,6 @@
bsPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * statsdPowerNas);
}
- public void testPowerBlameUid() throws Exception {
- if (statsdDisabled()) {
- return;
- }
- if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
- resetBatteryStats();
- unplugDevice();
-
- final double ALLOWED_FRACTIONAL_DIFFERENCE = 0.8; // ratio that statsd and bs can differ
-
- StatsdConfig.Builder config = getPulledConfig();
- addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_BLAME_UID_FIELD_NUMBER,
- null);
- uploadConfig(config);
- unplugDevice();
-
- Thread.sleep(WAIT_TIME_LONG);
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
- Thread.sleep(WAIT_TIME_LONG);
-
- setAppBreadcrumbPredicate();
- BatteryStatsProto batterystatsProto = getBatteryStatsProto();
- Thread.sleep(WAIT_TIME_LONG);
- List<Atom> atomList = getGaugeMetricDataList();
-
- // Extract statsd data
- boolean uidFound = false;
- int uid = getUid();
- long statsdUidPowerNas = 0;
- for (Atom atom : atomList) {
- DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
- if (item.getUid() == uid) {
- assertFalse("Found multiple power values for uid " + uid, uidFound);
- uidFound = true;
- statsdUidPowerNas = item.getPowerNanoAmpSecs();
- }
- }
- assertTrue("Statsd: No power value for uid " + uid, uidFound);
- assertTrue("Statsd: Non-positive power value for uid " + uid, statsdUidPowerNas > 0);
-
- // Extract batterystats data
- double bsUidPowerNas = -1;
- boolean hadUid = false;
- for (UidProto uidProto : batterystatsProto.getUidsList()) {
- if (uidProto.getUid() == uid) {
- hadUid = true;
- bsUidPowerNas = uidProto.getPowerUseItem().getComputedPowerMah()
- * 1_000_000L * 3600L; /* mAh to nAs */;
- }
- }
- assertTrue("Batterystats: No power value for uid " + uid, hadUid);
- assertTrue("BatteryStats: Non-positive power value for uid " + uid, bsUidPowerNas > 0);
-
- assertTrue(
- String.format("Statsd (%d) < Batterystats (%f).", statsdUidPowerNas, bsUidPowerNas),
- statsdUidPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * bsUidPowerNas);
- assertTrue(
- String.format("Batterystats (%f) < Statsd (%d).", bsUidPowerNas, statsdUidPowerNas),
- bsUidPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * statsdUidPowerNas);
- }
-
public void testServiceStartCount() throws Exception {
final String fileName = "BATTERYSTATS_SERVICE_START_COUNT.pbtxt";
StatsdConfig config = createValidationUtil().getConfig(fileName);
diff --git a/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java b/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java
index d6c9f1c..1d450cd 100644
--- a/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java
+++ b/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java
@@ -201,15 +201,17 @@
new LayoutInfo(R.layout.radiobutton, "radiobutton"),
new LayoutInfo(R.layout.radiogroup_horizontal, "radiogroup_horizontal"),
new LayoutInfo(R.layout.radiogroup_vertical, "radiogroup_vertical"),
- new LayoutInfo(R.layout.ratingbar_0, "ratingbar_0"),
- new LayoutInfo(R.layout.ratingbar_2point5, "ratingbar_2point5"),
- new LayoutInfo(R.layout.ratingbar_5, "ratingbar_5"),
- new LayoutInfo(R.layout.ratingbar_0, "ratingbar_0_pressed",
- new ViewPressedModifier()),
- new LayoutInfo(R.layout.ratingbar_2point5, "ratingbar_2point5_pressed",
- new ViewPressedModifier()),
- new LayoutInfo(R.layout.ratingbar_5, "ratingbar_5_pressed",
- new ViewPressedModifier()),
+ // Temporarily remove tests for the RatingBar widget. It has indeterminate rendering
+ // behavior on 360dpi devices, but we don't know why yet.
+ //new LayoutInfo(R.layout.ratingbar_0, "ratingbar_0"),
+ //new LayoutInfo(R.layout.ratingbar_2point5, "ratingbar_2point5"),
+ //new LayoutInfo(R.layout.ratingbar_5, "ratingbar_5"),
+ //new LayoutInfo(R.layout.ratingbar_0, "ratingbar_0_pressed",
+ // new ViewPressedModifier()),
+ //new LayoutInfo(R.layout.ratingbar_2point5, "ratingbar_2point5_pressed",
+ // new ViewPressedModifier()),
+ //new LayoutInfo(R.layout.ratingbar_5, "ratingbar_5_pressed",
+ // new ViewPressedModifier()),
new LayoutInfo(R.layout.searchview, "searchview_query",
new SearchViewModifier(SearchViewModifier.QUERY)),
new LayoutInfo(R.layout.searchview, "searchview_query_hint",
diff --git a/hostsidetests/userspacereboot/Android.bp b/hostsidetests/userspacereboot/Android.bp
index c3eb8b7..ba0715d 100644
--- a/hostsidetests/userspacereboot/Android.bp
+++ b/hostsidetests/userspacereboot/Android.bp
@@ -24,23 +24,11 @@
"hamcrest-library",
],
data: [
- ":UserspaceRebootTest",
+ ":BasicUserspaceRebootTestApp",
+ ":BootCompletedUserspaceRebootTestApp",
],
test_suites: [
"cts",
"general-tests",
],
}
-
-android_test_helper_app {
- name: "UserspaceRebootTest",
- srcs: ["app/src/**/*.java"],
- manifest : "app/AndroidManifest.xml",
- static_libs: [
- "androidx.test.runner",
- "androidx.test.core",
- "testng",
- "truth-prebuilt",
- ],
- sdk_version: "test_current",
-}
\ No newline at end of file
diff --git a/hostsidetests/userspacereboot/AndroidTest.xml b/hostsidetests/userspacereboot/AndroidTest.xml
index d62eb03..97f67af 100644
--- a/hostsidetests/userspacereboot/AndroidTest.xml
+++ b/hostsidetests/userspacereboot/AndroidTest.xml
@@ -20,10 +20,6 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="UserspaceRebootTest.apk" />
- </target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
<option name="class" value="com.android.cts.userspacereboot.host.UserspaceRebootHostTest" />
</test>
diff --git a/hostsidetests/userspacereboot/TEST_MAPPING b/hostsidetests/userspacereboot/TEST_MAPPING
new file mode 100644
index 0000000..1105edf
--- /dev/null
+++ b/hostsidetests/userspacereboot/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+ "presubmit" : [
+ {
+ "name": "CtsUserspaceRebootHostSideTestCases"
+ }
+ ]
+}
+
diff --git a/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java b/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java
index 9f3b4df..f953a1d 100644
--- a/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java
+++ b/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java
@@ -22,11 +22,13 @@
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
+import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,47 +40,58 @@
private static final String USERSPACE_REBOOT_SUPPORTED_PROP =
"ro.init.userspace_reboot.is_supported";
- /**
- * Runs the given {@code testName} of
- * {@link com.android.cts.userspacereboot.UserspaceRebootTest}.
- *
- * @throws Exception if test phase fails.
- */
- private void runDeviceTest(String testName) throws Exception {
- assertThat(runDeviceTests("com.android.cts.userspacereboot",
- "com.android.cts.userspacereboot.UserspaceRebootTest",
- testName)).isTrue();
+ private static final String BASIC_TEST_APP_APK = "BasicUserspaceRebootTestApp.apk";
+ private static final String BASIC_TEST_APP_PACKAGE_NAME =
+ "com.android.cts.userspacereboot.basic";
+
+ private static final String BOOT_COMPLETED_TEST_APP_APK =
+ "BootCompletedUserspaceRebootTestApp.apk";
+ private static final String BOOT_COMPLETED_TEST_APP_PACKAGE_NAME =
+ "com.android.cts.userspacereboot.bootcompleted";
+
+ private void runDeviceTest(String pkgName, String className, String testName) throws Exception {
+ assertThat(runDeviceTests(pkgName, pkgName + "." + className, testName)).isTrue();
+ }
+
+ private void installApk(String apkFileName) throws Exception {
+ CompatibilityBuildHelper helper = new CompatibilityBuildHelper(getBuild());
+ getDevice().installPackage(helper.getTestFile(apkFileName), false, true);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ getDevice().uninstallPackage(BASIC_TEST_APP_PACKAGE_NAME);
+ getDevice().uninstallPackage(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME);
}
@Test
public void testOnlyFbeDevicesSupportUserspaceReboot() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
+ installApk(BASIC_TEST_APP_APK);
assertThat(getDevice().getProperty("ro.crypto.state")).isEqualTo("encrypted");
assertThat(getDevice().getProperty("ro.crypto.type")).isEqualTo("file");
// Also verify that PowerManager.isRebootingUserspaceSupported will return true
- runDeviceTest("testUserspaceRebootIsSupported");
+ runDeviceTest(BASIC_TEST_APP_PACKAGE_NAME, "BasicUserspaceRebootTest",
+ "testUserspaceRebootIsSupported");
}
@Test
public void testDeviceDoesNotSupportUserspaceReboot() throws Exception {
assumeFalse("Userspace reboot supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
+ installApk(BASIC_TEST_APP_APK);
// Also verify that PowerManager.isRebootingUserspaceSupported will return true
- runDeviceTest("testUserspaceRebootIsNotSupported");
+ runDeviceTest(BASIC_TEST_APP_PACKAGE_NAME, "BasicUserspaceRebootTest",
+ "testUserspaceRebootIsNotSupported");
}
@Test
public void testUserspaceReboot() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
- getDevice().rebootUserspace();
- assertWithMessage("Device did not boot within 2 minutes").that(
- getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
- // If userspace reboot fails and fallback to hard reboot is triggered then
- // sys.init.userspace_reboot.last_finished won't be set.
- assertWithMessage("Userspace reboot failed and fallback to full reboot was triggered").that(
- getDevice().getProperty("sys.init.userspace_reboot.last_finished")).isNotEmpty();
+ rebootUserspaceAndWaitForBootComplete();
+ assertUserspaceRebootSucceed();
}
@Test
@@ -90,13 +103,32 @@
Thread.sleep(500);
assertWithMessage("Failed to start checkpoint : %s", result.getStderr()).that(
result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
- getDevice().rebootUserspace();
- assertWithMessage("Device did not boot withing 2 minutes").that(
- getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
- // If userspace reboot fails and fallback to hard reboot is triggered then
- // sys.init.userspace_reboot.last_finished won't be set.
- assertWithMessage("Userspace reboot failed and fallback to full reboot was triggered").that(
- getDevice().getProperty("sys.init.userspace_reboot.last_finished")).isNotEmpty();
+ rebootUserspaceAndWaitForBootComplete();
+ assertUserspaceRebootSucceed();
+ }
+
+ // TODO(ioffe): this should also cover other lock scenarios.
+ @Test
+ public void testUserspaceReboot_verifyCeStorageIsUnlocked() throws Exception {
+ assumeTrue("Userspace reboot not supported on the device",
+ getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
+ try {
+ getDevice().executeShellV2Command("cmd lock_settings set-pin 1543");
+ installApk(BOOT_COMPLETED_TEST_APP_APK);
+ runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
+ "prepareFile");
+ rebootUserspaceAndWaitForBootComplete();
+ assertUserspaceRebootSucceed();
+ // Sleep for 30s to make sure that system_server has sent out BOOT_COMPLETED broadcast.
+ Thread.sleep(Duration.ofSeconds(30).toMillis());
+ getDevice().executeShellV2Command("am wait-for-broadcast-idle");
+ runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
+ "testVerifyCeStorageUnlocked");
+ runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
+ "testVerifyReceivedBootCompletedBroadcast");
+ } finally {
+ getDevice().executeShellV2Command("cmd lock_settings clear --old 1543");
+ }
}
@Test
@@ -121,4 +153,18 @@
result.getStderr()).that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
return "true".equals(result.getStdout().trim());
}
+
+ private void rebootUserspaceAndWaitForBootComplete() throws Exception {
+ getDevice().setProperty("test.userspace_reboot.requested", "1");
+ getDevice().rebootUserspace();
+ assertWithMessage("Device did not boot withing 2 minutes").that(
+ getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
+ }
+
+ private void assertUserspaceRebootSucceed() throws Exception {
+ // If userspace reboot fails and fallback to hard reboot is triggered then
+ // test.userspace_reboot.requested won't be set.
+ assertWithMessage("Userspace reboot failed and fallback to full reboot was triggered").that(
+ getDevice().getProperty("test.userspace_reboot.requested")).isEqualTo("1");
+ }
}
diff --git a/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp b/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp
new file mode 100644
index 0000000..53ef0f1a
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2020 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.
+
+android_test_helper_app {
+ name: "BasicUserspaceRebootTestApp",
+ srcs: ["src/**/*.java"],
+ manifest : "AndroidManifest.xml",
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.core",
+ "testng",
+ "truth-prebuilt",
+ ],
+ min_sdk_version: "30",
+}
\ No newline at end of file
diff --git a/hostsidetests/userspacereboot/app/AndroidManifest.xml b/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
similarity index 86%
rename from hostsidetests/userspacereboot/app/AndroidManifest.xml
rename to hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
index 7eb103d..f003a71 100644
--- a/hostsidetests/userspacereboot/app/AndroidManifest.xml
+++ b/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
@@ -16,13 +16,13 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.userspacereboot" >
+ package="com.android.cts.userspacereboot.basic" >
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.cts.userspacereboot"
- android:label="UserspaceReboot device side tests"/>
+ android:targetPackage="com.android.cts.userspacereboot.basic"
+ android:label="Basic userspace reboot device side tests"/>
</manifest>
diff --git a/hostsidetests/userspacereboot/app/src/com/android/cts/userspacereboot/UserspaceRebootTest.java b/hostsidetests/userspacereboot/testapps/BasicTestApp/src/com/android/cts/userspacereboot/basic/BasicUserspaceRebootTest.java
similarity index 94%
rename from hostsidetests/userspacereboot/app/src/com/android/cts/userspacereboot/UserspaceRebootTest.java
rename to hostsidetests/userspacereboot/testapps/BasicTestApp/src/com/android/cts/userspacereboot/basic/BasicUserspaceRebootTest.java
index 341ab1c..f4616c2 100644
--- a/hostsidetests/userspacereboot/app/src/com/android/cts/userspacereboot/UserspaceRebootTest.java
+++ b/hostsidetests/userspacereboot/testapps/BasicTestApp/src/com/android/cts/userspacereboot/basic/BasicUserspaceRebootTest.java
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.cts.userspacereboot;
+package com.android.cts.userspacereboot.basic;
import static com.google.common.truth.Truth.assertThat;
-
import static org.testng.Assert.assertThrows;
import android.content.Context;
@@ -31,7 +30,7 @@
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
-public class UserspaceRebootTest {
+public class BasicUserspaceRebootTest {
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
@@ -48,4 +47,5 @@
assertThrows(UnsupportedOperationException.class,
() -> powerManager.reboot("userspace"));
}
+
}
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
new file mode 100644
index 0000000..76357a6
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2020 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.
+
+android_test_helper_app {
+ name: "BootCompletedUserspaceRebootTestApp",
+ srcs: ["src/**/*.java"],
+ manifest : "AndroidManifest.xml",
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.core",
+ "testng",
+ "truth-prebuilt",
+ ],
+ min_sdk_version: "30",
+}
\ No newline at end of file
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/AndroidManifest.xml b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..bfa53f9
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.userspacereboot.bootcompleted" >
+
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <application>
+ <activity android:name=".LauncherActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <uses-library android:name="android.test.runner" />
+ <receiver android:name=".BootCompletedUserspaceRebootTest$BootReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.userspacereboot.bootcompleted"
+ android:label="Boot Completed userspace reboot device side tests"/>
+</manifest>
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/BootCompletedUserspaceRebootTest.java b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/BootCompletedUserspaceRebootTest.java
new file mode 100644
index 0000000..8bb86ce
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/BootCompletedUserspaceRebootTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 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.userspacereboot.bootcompleted;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserManager;
+import android.util.Log;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.Scanner;
+
+@RunWith(JUnit4.class)
+public class BootCompletedUserspaceRebootTest {
+
+ private static final String TAG = "UserspaceRebootTest";
+
+ private static final String FILE_NAME = "secret.txt";
+ private static final String SECRET_MESSAGE = "wow, much secret";
+
+ private static final String RECEIVED_BROADCASTS_FILE = "received_broadcasts.txt";
+
+ private final Context mCeContext =
+ getInstrumentation().getContext().createCredentialProtectedStorageContext();
+ private final Context mDeContext =
+ getInstrumentation().getContext().createDeviceProtectedStorageContext();
+
+ @Test
+ public void prepareFile() throws Exception {
+ try (OutputStreamWriter writer = new OutputStreamWriter(
+ mCeContext.openFileOutput(FILE_NAME, Context.MODE_PRIVATE))) {
+ writer.write(SECRET_MESSAGE);
+ }
+ }
+
+ @Test
+ public void testVerifyCeStorageUnlocked() throws Exception {
+ UserManager um = getInstrumentation().getContext().getSystemService(UserManager.class);
+ assertThat(um.isUserUnlocked(0)).isTrue();
+ try (Scanner scanner = new Scanner(mCeContext.openFileInput(FILE_NAME))) {
+ final String content = scanner.nextLine();
+ assertThat(content).isEqualTo(SECRET_MESSAGE);
+ }
+ }
+
+ @Test
+ public void testVerifyReceivedBootCompletedBroadcast() throws Exception {
+ try (Scanner scanner = new Scanner(mDeContext.openFileInput(RECEIVED_BROADCASTS_FILE))) {
+ final String intent = scanner.nextLine();
+ assertThat(intent).isEqualTo(Intent.ACTION_BOOT_COMPLETED);
+ }
+ }
+
+ public static class BootReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Received! " + intent);
+ try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(
+ context.createDeviceProtectedStorageContext().openFileOutput(
+ RECEIVED_BROADCASTS_FILE, Context.MODE_PRIVATE)))) {
+ writer.println(intent.getAction());
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to append to " + RECEIVED_BROADCASTS_FILE, e);
+ }
+ }
+ }
+}
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/LauncherActivity.java b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/LauncherActivity.java
new file mode 100644
index 0000000..abaca60
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/LauncherActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.userspacereboot.bootcompleted;
+
+import android.app.Activity;
+
+public class LauncherActivity extends Activity {
+}
diff --git a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
index e01d682..d8ad039 100644
--- a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
+++ b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
@@ -153,5 +153,7 @@
} finally {
in.close();
}
+ //Re-enable Wi-Fi as part of CTS Pre-conditions
+ device.executeShellCommand("svc wifi enable; sleep 1");
}
}
diff --git a/tests/app/src/android/app/cts/DialogTest.java b/tests/app/src/android/app/cts/DialogTest.java
index 00837f6..2838830 100755
--- a/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/app/src/android/app/cts/DialogTest.java
@@ -485,7 +485,7 @@
d.getWindow().getDecorView().getRootView().getLocationOnScreen(dialogLocation);
final int touchSlop = ViewConfiguration.get(mActivity).getScaledWindowTouchSlop();
- final int x = dialogLocation[0] - (touchSlop + 1);
+ final int x = dialogLocation[0];
final int y = dialogLocation[1] - (touchSlop + 1);
assertNull(d.onTouchEvent);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 691ee97..9b3d9b9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -53,7 +53,6 @@
import org.junit.Rule;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
-import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
@@ -199,9 +198,15 @@
protected static final RequiredFeatureRule sRequiredFeatureRule =
new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
- private final TestWatcher mTestWatcher = new AutofillTestWatcher();
+ private final AutofillTestWatcher mTestWatcher = new AutofillTestWatcher();
- private final RetryRule mRetryRule = new RetryRule(getNumberRetries());
+ private final RetryRule mRetryRule =
+ new RetryRule(getNumberRetries(), () -> {
+ // Between testing and retries, clean all launched activities to avoid
+ // exception:
+ // Could not launch intent Intent { ... } within 45 seconds.
+ mTestWatcher.cleanAllActivities();
+ });
private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
index 6879a8c..6c29197 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
@@ -36,6 +36,18 @@
*/
public final class AutofillTestWatcher extends TestWatcher {
+ /**
+ * Cleans up all launched activities between the tests and retries.
+ */
+ public void cleanAllActivities() {
+ try {
+ finishActivities();
+ waitUntilAllDestroyed();
+ } finally {
+ resetStaticState();
+ }
+ }
+
private static final String TAG = "AutofillTestWatcher";
@GuardedBy("sUnfinishedBusiness")
@@ -55,12 +67,7 @@
@Override
protected void finished(Description description) {
final String testName = description.getDisplayName();
- try {
- finishActivities();
- waitUntilAllDestroyed();
- } finally {
- resetStaticState();
- }
+ cleanAllActivities();
Log.i(TAG, "Finished " + testName);
TestNameUtils.setCurrentTestName(null);
}
diff --git a/tests/camera/libctscamera2jni/native-camera-jni.cpp b/tests/camera/libctscamera2jni/native-camera-jni.cpp
index b9fc256..5daccb7 100644
--- a/tests/camera/libctscamera2jni/native-camera-jni.cpp
+++ b/tests/camera/libctscamera2jni/native-camera-jni.cpp
@@ -2721,10 +2721,12 @@
goto cleanup;
}
+ StaticInfo staticInfo(chars);
ACameraMetadata_const_entry sessionParamKeys{};
ret = ACameraMetadata_getConstEntry(chars, ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS,
&sessionParamKeys);
- if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0)) {
+ if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0) ||
+ !staticInfo.isColorOutputSupported()) {
ACameraMetadata_free(chars);
chars = nullptr;
continue;
@@ -3167,7 +3169,7 @@
}
StaticInfo staticInfo(chars);
- usleep(100000); // sleep to give some time for callbacks to happen
+ usleep(200000); // sleep to give some time for callbacks to happen
if (testCase.isCameraAvailable(cameraId)) {
LOG_ERROR(errorString, "Camera %s should be unavailable now", cameraId);
@@ -3262,7 +3264,7 @@
}
// wait until last sequence complete
- resultListener.getCaptureSequenceLastFrameNumber(lastSeqId, /*timeoutSec*/ 3);
+ resultListener.getCaptureSequenceLastFrameNumber(lastSeqId, /*timeoutSec*/ 5);
std::vector<ACaptureRequest*> completedRequests;
resultListener.getCompletedRequests(&completedRequests);
@@ -3333,7 +3335,7 @@
goto cleanup;
}
- usleep(100000); // sleep to give some time for callbacks to happen
+ usleep(200000); // sleep to give some time for callbacks to happen
if (!testCase.isCameraAvailable(cameraId)) {
LOG_ERROR(errorString, "Camera %s should be available now", cameraId);
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index d108600e..3556954 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -546,7 +546,7 @@
MandatoryStreamCombination combination, boolean substituteY8,
boolean substituteHeic) {
- final int TIMEOUT_FOR_RESULT_MS = 3000;
+ final int TIMEOUT_FOR_RESULT_MS = 5000;
final int NUM_REPROCESS_CAPTURES_PER_CONFIG = 3;
List<SurfaceTexture> privTargets = new ArrayList<>();
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
index 4cd0046..674dc96 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -61,7 +61,7 @@
// Default capture size: VGA size is required by CDD.
protected static final Size DEFAULT_CAPTURE_SIZE = new Size(640, 480);
- protected static final int CAPTURE_WAIT_TIMEOUT_MS = 5000;
+ protected static final int CAPTURE_WAIT_TIMEOUT_MS = 7000;
protected CameraManager mCameraManager;
protected CameraDevice mCamera;
diff --git a/tests/contentcaptureservice/AndroidTest.xml b/tests/contentcaptureservice/AndroidTest.xml
index f8afb9a..13866c4 100644
--- a/tests/contentcaptureservice/AndroidTest.xml
+++ b/tests/contentcaptureservice/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="contentcapture" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index a674c86..13f0f9b 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -152,7 +152,9 @@
<activity android:name="android.server.wm.StartActivityTests$TestActivity2" />
- <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity" />
+ <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity"
+ android:resizeableActivity="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen" />
<activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity2" />
<activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivityWithBrokenContextWrapper" />
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
index 07a8dba..3faa455 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -44,6 +44,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -56,6 +57,7 @@
import android.server.wm.TestJournalProvider.TestJournalContainer;
import android.server.wm.WindowManagerState.Display;
import android.server.wm.WindowManagerState.WindowState;
+import android.server.wm.intent.Activities;
import android.text.TextUtils;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
@@ -587,6 +589,60 @@
}
}
+ @Test
+ public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+ // Launch a regular activity on default display at the test beginning to prevent the
+ // test may mis-touch the launcher icon that breaks the test expectation.
+ final TestActivitySession<Activities.RegularActivity> testActivitySession = new
+ TestActivitySession<>();
+ final TestActivitySession<ImeTestActivity> imeTestActivitySession = new
+ TestActivitySession<>();
+ // Leverage MockImeSession to ensure at least an IME exists as default.
+ final MockImeSession mockImeSession = MockImeSession.create(
+ mContext, getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
+
+ testActivitySession.launchTestActivityOnDisplaySync(Activities.RegularActivity.class,
+ DEFAULT_DISPLAY);
+
+ // Create a virtual display and launch an activity on virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession
+ .setShowSystemDecorations(true)
+ .setSimulateDisplay(true)
+ .createDisplay();
+
+ imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
+ newDisplay.mId);
+
+ // Tap default display, assume a pointer-out-side event will happened to change the top
+ // display.
+ final Display defDisplay = mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY);
+ tapOnDisplayCenter(defDisplay.getDisplayId());
+
+ // Reparent ImeTestActivity from virtual display to default display.
+ getLaunchActivityBuilder()
+ .setUseInstrumentation()
+ .setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
+ .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .allowMultipleInstances(false)
+ .setDisplayId(DEFAULT_DISPLAY).execute();
+ waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
+ DEFAULT_DISPLAY, "Activity launched on default display and on top");
+ assertEquals("No stacks on virtual display", 0,
+ mAmWmState.getAmState().getDisplay(newDisplay.mId).mStacks.size());
+
+ // Verify if tapping default display to request focus on EditText can show soft input.
+ final ImeEventStream stream = mockImeSession.openEventStream();
+ tapOnDisplayCenter(defDisplay.getDisplayId());
+ imeTestActivitySession.runOnMainSyncAndWait(
+ imeTestActivitySession.getActivity()::showSoftInput);
+ waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.getDisplayId(),
+ editorMatcher("onStartInput",
+ imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
+ }
+ }
+
public static class ImeTestActivity extends Activity {
ImeAwareEditText mEditText;
diff --git a/tests/media/Android.bp b/tests/media/Android.bp
index 889b123..b813fb2 100644
--- a/tests/media/Android.bp
+++ b/tests/media/Android.bp
@@ -26,6 +26,7 @@
],
jni_libs: [
"libctsmediav2muxer_jni",
+ "libctsmediav2extractor_jni",
],
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
diff --git a/tests/media/OWNERS b/tests/media/OWNERS
index 8ffa99e..fad6041 100644
--- a/tests/media/OWNERS
+++ b/tests/media/OWNERS
@@ -1,12 +1,2 @@
# Bug component: 1344
-andrewlewis@google.com
-chz@google.com
-dwkang@google.com
-elaurent@google.com
-essick@google.com
-gkasten@google.com
-hunga@google.com
-jmtrivi@google.com
-lajos@google.com
-marcone@google.com
-wjia@google.com
+include ../tests/media/OWNERS
diff --git a/tests/media/jni/Android.bp b/tests/media/jni/Android.bp
index e151d37..10f0f59 100644
--- a/tests/media/jni/Android.bp
+++ b/tests/media/jni/Android.bp
@@ -17,6 +17,25 @@
srcs: [
"NativeMediaConstants.cpp",
"NativeMuxerTest.cpp",
+ "NativeMuxerUnitTest.cpp",
+ ],
+ shared_libs: [
+ "libmediandk",
+ "liblog",
+ ],
+ stl: "libc++_static",
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+ gtest: false,
+}
+
+cc_test_library {
+ name: "libctsmediav2extractor_jni",
+ srcs: [
+ "NativeMediaConstants.cpp",
+ "NativeExtractorTest.cpp",
],
shared_libs: [
"libmediandk",
diff --git a/tests/media/jni/NativeExtractorTest.cpp b/tests/media/jni/NativeExtractorTest.cpp
new file mode 100644
index 0000000..42876f0
--- /dev/null
+++ b/tests/media/jni/NativeExtractorTest.cpp
@@ -0,0 +1,735 @@
+/*
+ * Copyright (C) 2019 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 "NativeExtractorTest"
+#include <log/log.h>
+
+#include <jni.h>
+#include <sys/stat.h>
+
+#include <cstdlib>
+#include <random>
+
+#include "media/NdkMediaExtractor.h"
+
+static bool isExtractorOKonEOS(AMediaExtractor* extractor) {
+ return AMediaExtractor_getSampleTrackIndex(extractor) < 0 &&
+ AMediaExtractor_getSampleSize(extractor) < 0 &&
+ (int)AMediaExtractor_getSampleFlags(extractor) < 0 &&
+ AMediaExtractor_getSampleTime(extractor) < 0;
+}
+
+static bool isSampleInfoIdentical(AMediaCodecBufferInfo* refSample,
+ AMediaCodecBufferInfo* testSample) {
+ return refSample->flags == testSample->flags && refSample->size == testSample->size &&
+ refSample->presentationTimeUs == testSample->presentationTimeUs;
+}
+
+static bool isSampleInfoValidAndIdentical(AMediaCodecBufferInfo* refSample,
+ AMediaCodecBufferInfo* testSample) {
+ return refSample->flags == testSample->flags && refSample->size == testSample->size &&
+ abs(refSample->presentationTimeUs - testSample->presentationTimeUs) <= 1 &&
+ (int)refSample->flags >= 0 && refSample->size >= 0 && refSample->presentationTimeUs >= 0;
+}
+
+static bool isFormatSimilar(AMediaFormat* refFormat, AMediaFormat* testFormat) {
+ const char *refMime = nullptr, *testMime = nullptr;
+ bool hasRefMime = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
+ bool hasTestMime = AMediaFormat_getString(testFormat, AMEDIAFORMAT_KEY_MIME, &testMime);
+
+ if (!hasRefMime || !hasTestMime || strcmp(refMime, testMime) != 0) return false;
+ if (!strncmp(refMime, "audio/", strlen("audio/"))) {
+ int32_t refSampleRate, testSampleRate, refNumChannels, testNumChannels;
+ bool hasRefSampleRate =
+ AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &refSampleRate);
+ bool hasTestSampleRate =
+ AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &testSampleRate);
+ bool hasRefNumChannels =
+ AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &refNumChannels);
+ bool hasTestNumChannels =
+ AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &testNumChannels);
+ return hasRefSampleRate && hasTestSampleRate && hasRefNumChannels && hasTestNumChannels &&
+ refNumChannels == testNumChannels && refSampleRate == testSampleRate;
+ } else if (!strncmp(refMime, "video/", strlen("video/"))) {
+ int32_t refWidth, testWidth, refHeight, testHeight;
+ bool hasRefWidth = AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_WIDTH, &refWidth);
+ bool hasTestWidth = AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_WIDTH, &testWidth);
+ bool hasRefHeight = AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_HEIGHT, &refHeight);
+ bool hasTestHeight =
+ AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_HEIGHT, &testHeight);
+ return hasRefWidth && hasTestWidth && hasRefHeight && hasTestHeight &&
+ refWidth == testWidth && refHeight == testHeight;
+ }
+ return true;
+}
+
+static void inline setSampleInfo(AMediaExtractor* extractor, AMediaCodecBufferInfo* info) {
+ info->flags = AMediaExtractor_getSampleFlags(extractor);
+ info->offset = 0;
+ info->size = AMediaExtractor_getSampleSize(extractor);
+ info->presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
+}
+
+static bool isMediaSimilar(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor,
+ const char* mime, int sampleLimit = INT32_MAX) {
+ const int maxSampleSize = (4 * 1024 * 1024);
+ auto refBuffer = new uint8_t[maxSampleSize];
+ auto testBuffer = new uint8_t[maxSampleSize];
+ int noOfTracksMatched = 0;
+ for (size_t refTrackID = 0; refTrackID < AMediaExtractor_getTrackCount(refExtractor);
+ refTrackID++) {
+ AMediaFormat* refFormat = AMediaExtractor_getTrackFormat(refExtractor, refTrackID);
+ const char* refMime = nullptr;
+ bool hasKey = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
+ if (!hasKey || (mime != nullptr && strcmp(refMime, mime) != 0)) {
+ AMediaFormat_delete(refFormat);
+ continue;
+ }
+ for (size_t testTrackID = 0; testTrackID < AMediaExtractor_getTrackCount(testExtractor);
+ testTrackID++) {
+ AMediaFormat* testFormat = AMediaExtractor_getTrackFormat(testExtractor, testTrackID);
+ if (!isFormatSimilar(refFormat, testFormat)) {
+ AMediaFormat_delete(testFormat);
+ continue;
+ }
+ AMediaExtractor_selectTrack(refExtractor, refTrackID);
+ AMediaExtractor_selectTrack(testExtractor, testTrackID);
+
+ AMediaCodecBufferInfo refSampleInfo, testSampleInfo;
+ bool areTracksIdentical = true;
+ for (int frameCount = 0;; frameCount++) {
+ setSampleInfo(refExtractor, &refSampleInfo);
+ setSampleInfo(testExtractor, &testSampleInfo);
+ if (!isSampleInfoValidAndIdentical(&refSampleInfo, &testSampleInfo)) {
+ ALOGD(" Mime: %s mismatch for sample: %d", refMime, frameCount);
+ ALOGD(" flags exp/got: %d / %d", refSampleInfo.flags, testSampleInfo.flags);
+ ALOGD(" size exp/got: %d / %d ", refSampleInfo.size, testSampleInfo.size);
+ ALOGD(" ts exp/got: %d / %d ", (int)refSampleInfo.presentationTimeUs,
+ (int)testSampleInfo.presentationTimeUs);
+ areTracksIdentical = false;
+ break;
+ }
+ ssize_t refSz =
+ AMediaExtractor_readSampleData(refExtractor, refBuffer, maxSampleSize);
+ if (refSz != refSampleInfo.size) {
+ ALOGD("Mime: %s Size exp/got: %d / %zd ", refMime, refSampleInfo.size, refSz);
+ areTracksIdentical = false;
+ break;
+ }
+ ssize_t testSz =
+ AMediaExtractor_readSampleData(testExtractor, testBuffer, maxSampleSize);
+ if (testSz != testSampleInfo.size) {
+ ALOGD("Mime: %s Size exp/got: %d / %zd ", refMime, testSampleInfo.size,
+ testSz);
+ areTracksIdentical = false;
+ break;
+ }
+ int trackIndex = AMediaExtractor_getSampleTrackIndex(refExtractor);
+ if (trackIndex != refTrackID) {
+ ALOGD("Mime: %s TrackID exp/got: %zu / %d", refMime, refTrackID, trackIndex);
+ areTracksIdentical = false;
+ break;
+ }
+ trackIndex = AMediaExtractor_getSampleTrackIndex(testExtractor);
+ if (trackIndex != testTrackID) {
+ ALOGD("Mime: %s TrackID exp/got %zd / %d : ", refMime, testTrackID,
+ trackIndex);
+ areTracksIdentical = false;
+ break;
+ }
+ bool haveRefSamples = AMediaExtractor_advance(refExtractor);
+ bool haveTestSamples = AMediaExtractor_advance(testExtractor);
+ if (haveRefSamples != haveTestSamples) {
+ ALOGD("Mime: %s Mismatch in sampleCount", refMime);
+ areTracksIdentical = false;
+ break;
+ }
+
+ if (!haveRefSamples && !isExtractorOKonEOS(refExtractor)) {
+ ALOGD("Mime: %s calls post advance() are not OK", refMime);
+ areTracksIdentical = false;
+ break;
+ }
+ if (!haveTestSamples && !isExtractorOKonEOS(testExtractor)) {
+ ALOGD("Mime: %s calls post advance() are not OK", refMime);
+ areTracksIdentical = false;
+ break;
+ }
+ ALOGV("Mime: %s Sample: %d flags: %d size: %d ts: %d", refMime, frameCount,
+ refSampleInfo.flags, refSampleInfo.size,
+ (int)refSampleInfo.presentationTimeUs);
+ if (!haveRefSamples || frameCount >= sampleLimit) {
+ break;
+ }
+ }
+ AMediaExtractor_unselectTrack(testExtractor, testTrackID);
+ AMediaExtractor_unselectTrack(refExtractor, refTrackID);
+ AMediaFormat_delete(testFormat);
+ if (areTracksIdentical) {
+ noOfTracksMatched++;
+ break;
+ }
+ }
+ AMediaFormat_delete(refFormat);
+ if (mime != nullptr && noOfTracksMatched > 0) break;
+ }
+ delete[] refBuffer;
+ delete[] testBuffer;
+ if (mime == nullptr) {
+ return noOfTracksMatched == AMediaExtractor_getTrackCount(refExtractor);
+ } else {
+ return noOfTracksMatched > 0;
+ }
+}
+
+static bool validateCachedDuration(AMediaExtractor* extractor, bool isNetworkSource) {
+ if (isNetworkSource) {
+ AMediaExtractor_selectTrack(extractor, 0);
+ for (unsigned cnt = 0;; cnt++) {
+ if ((cnt & (cnt - 1)) == 0) {
+ if (AMediaExtractor_getCachedDuration(extractor) < 0) {
+ ALOGE("getCachedDuration is less than zero for network source");
+ return false;
+ }
+ }
+ if (!AMediaExtractor_advance(extractor)) break;
+ }
+ AMediaExtractor_unselectTrack(extractor, 0);
+ } else {
+ if (AMediaExtractor_getCachedDuration(extractor) != -1) {
+ ALOGE("getCachedDuration != -1 for non-network source");
+ return false;
+ }
+ }
+ return true;
+}
+
+static AMediaExtractor* createExtractorFromFD(FILE* fp) {
+ AMediaExtractor* extractor = nullptr;
+ struct stat buf {};
+ if (fp && !fstat(fileno(fp), &buf)) {
+ extractor = AMediaExtractor_new();
+ media_status_t res = AMediaExtractor_setDataSourceFd(extractor, fileno(fp), 0, buf.st_size);
+ if (res != AMEDIA_OK) {
+ AMediaExtractor_delete(extractor);
+ extractor = nullptr;
+ }
+ }
+ return extractor;
+}
+
+// content necessary for testing seek are grouped in this class
+class SeekTestParams {
+ public:
+ SeekTestParams(AMediaCodecBufferInfo expected, int64_t timeStamp, SeekMode mode)
+ : mExpected{expected}, mTimeStamp{timeStamp}, mMode{mode} {}
+
+ AMediaCodecBufferInfo mExpected;
+ int64_t mTimeStamp;
+ SeekMode mMode;
+};
+
+static std::vector<AMediaCodecBufferInfo*> getSeekablePoints(const char* srcFile,
+ const char* mime) {
+ std::vector<AMediaCodecBufferInfo*> bookmarks;
+ if (mime == nullptr) return bookmarks;
+ FILE* srcFp = fopen(srcFile, "rbe");
+ if (!srcFp) {
+ ALOGE("fopen failed for srcFile %s", srcFile);
+ return bookmarks;
+ }
+ AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+ if (!extractor) {
+ if (srcFp) fclose(srcFp);
+ ALOGE("createExtractorFromFD failed");
+ return bookmarks;
+ }
+
+ for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
+ AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
+ const char* currMime = nullptr;
+ bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
+ if (!hasKey || strcmp(currMime, mime) != 0) {
+ AMediaFormat_delete(format);
+ continue;
+ }
+ AMediaExtractor_selectTrack(extractor, trackID);
+ do {
+ uint32_t sampleFlags = AMediaExtractor_getSampleFlags(extractor);
+ if ((sampleFlags & AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC) != 0) {
+ auto sampleInfo = new AMediaCodecBufferInfo;
+ setSampleInfo(extractor, sampleInfo);
+ bookmarks.push_back(sampleInfo);
+ }
+ } while (AMediaExtractor_advance(extractor));
+ AMediaExtractor_unselectTrack(extractor, trackID);
+ AMediaFormat_delete(format);
+ break;
+ }
+ AMediaExtractor_delete(extractor);
+ if (srcFp) fclose(srcFp);
+ return bookmarks;
+}
+
+static constexpr unsigned kSeed = 0x7ab7;
+
+static std::vector<SeekTestParams*> generateSeekTestArgs(const char* srcFile, const char* mime,
+ bool isRandom) {
+ std::vector<SeekTestParams*> testArgs;
+ if (mime == nullptr) return testArgs;
+ const int MAX_SEEK_POINTS = 7;
+ std::srand(kSeed);
+ if (isRandom) {
+ FILE* srcFp = fopen(srcFile, "rbe");
+ if (!srcFp) {
+ ALOGE("fopen failed for srcFile %s", srcFile);
+ return testArgs;
+ }
+ AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+ if (!extractor) {
+ if (srcFp) fclose(srcFp);
+ ALOGE("createExtractorFromFD failed");
+ return testArgs;
+ }
+
+ const int64_t maxEstDuration = 4000000;
+ for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
+ AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
+ const char* currMime = nullptr;
+ bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
+ if (!hasKey || strcmp(currMime, mime) != 0) {
+ AMediaFormat_delete(format);
+ continue;
+ }
+ AMediaExtractor_selectTrack(extractor, trackID);
+ for (int i = 0; i < MAX_SEEK_POINTS; i++) {
+ double r = ((double)rand() / (RAND_MAX));
+ long pts = (long)(r * maxEstDuration);
+
+ for (int mode = AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC;
+ mode <= AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC; mode++) {
+ AMediaExtractor_seekTo(extractor, pts, (SeekMode)mode);
+ AMediaCodecBufferInfo currInfo;
+ setSampleInfo(extractor, &currInfo);
+ testArgs.push_back((new SeekTestParams(currInfo, pts, (SeekMode)mode)));
+ }
+ }
+ AMediaExtractor_unselectTrack(extractor, trackID);
+ AMediaFormat_delete(format);
+ break;
+ }
+ AMediaExtractor_delete(extractor);
+ if (srcFp) fclose(srcFp);
+ } else {
+ std::vector<AMediaCodecBufferInfo*> bookmarks = getSeekablePoints(srcFile, mime);
+ if (bookmarks.empty()) return testArgs;
+ int size = bookmarks.size();
+ int* indices;
+ int indexSize = 0;
+ if (size > MAX_SEEK_POINTS) {
+ indices = new int[MAX_SEEK_POINTS];
+ indexSize = MAX_SEEK_POINTS;
+ indices[0] = 0;
+ indices[MAX_SEEK_POINTS - 1] = size - 1;
+ for (int i = 1; i < MAX_SEEK_POINTS - 1; i++) {
+ double r = ((double)rand() / (RAND_MAX));
+ indices[i] = (int)(r * (MAX_SEEK_POINTS - 1) + 1);
+ }
+ } else {
+ indices = new int[size];
+ indexSize = size;
+ for (int i = 0; i < size; i++) indices[i] = i;
+ }
+ for (int i = 0; i < indexSize; i++) {
+ AMediaCodecBufferInfo currInfo = *bookmarks[i];
+ int64_t pts = currInfo.presentationTimeUs;
+ testArgs.push_back(
+ (new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC)));
+ testArgs.push_back((new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC)));
+ testArgs.push_back(
+ (new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC)));
+ if (i > 0) {
+ AMediaCodecBufferInfo prevInfo = *bookmarks[i - 1];
+ int64_t ptsMinus = prevInfo.presentationTimeUs;
+ ptsMinus = pts - ((pts - ptsMinus) >> 3);
+ testArgs.push_back((
+ new SeekTestParams(currInfo, ptsMinus, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC)));
+ testArgs.push_back(
+ (new SeekTestParams(currInfo, ptsMinus, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC)));
+ testArgs.push_back((new SeekTestParams(prevInfo, ptsMinus,
+ AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC)));
+ }
+ if (i < size - 1) {
+ AMediaCodecBufferInfo nextInfo = *bookmarks[i + 1];
+ int64_t ptsPlus = nextInfo.presentationTimeUs;
+ ptsPlus = pts + ((ptsPlus - pts) >> 3);
+ testArgs.push_back(
+ (new SeekTestParams(currInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC)));
+ testArgs.push_back(
+ (new SeekTestParams(nextInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC)));
+ testArgs.push_back((
+ new SeekTestParams(currInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC)));
+ }
+ }
+ for (auto bookmark : bookmarks) {
+ delete bookmark;
+ }
+ bookmarks.clear();
+ delete[] indices;
+ }
+ return testArgs;
+}
+
+static int checkSeekPoints(const char* srcFile, const char* mime,
+ const std::vector<SeekTestParams*>& seekTestArgs) {
+ int errCnt = 0;
+ FILE* srcFp = fopen(srcFile, "rbe");
+ AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+ if (!extractor) {
+ if (srcFp) fclose(srcFp);
+ ALOGE("createExtractorFromFD failed");
+ return -1;
+ }
+ for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
+ AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
+ const char* currMime = nullptr;
+ bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
+ if (!hasKey || strcmp(currMime, mime) != 0) {
+ continue;
+ }
+ AMediaExtractor_selectTrack(extractor, trackID);
+ AMediaCodecBufferInfo received;
+ for (auto arg : seekTestArgs) {
+ AMediaExtractor_seekTo(extractor, arg->mTimeStamp, arg->mMode);
+ setSampleInfo(extractor, &received);
+ if (!isSampleInfoIdentical(&arg->mExpected, &received)) {
+ ALOGE(" flags exp/got: %d / %d", arg->mExpected.flags, received.flags);
+ ALOGE(" size exp/got: %d / %d ", arg->mExpected.size, received.size);
+ ALOGE(" ts exp/got: %d / %d ", (int)arg->mExpected.presentationTimeUs,
+ (int)received.presentationTimeUs);
+ errCnt++;
+ }
+ }
+ AMediaExtractor_unselectTrack(extractor, trackID);
+ AMediaFormat_delete(format);
+ break;
+ }
+ AMediaExtractor_delete(extractor);
+ if (srcFp) fclose(srcFp);
+ return errCnt;
+}
+
+static bool isFileFormatIdentical(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor) {
+ bool result = false;
+ if (refExtractor && testExtractor) {
+ AMediaFormat* refFormat = AMediaExtractor_getFileFormat(refExtractor);
+ AMediaFormat* testFormat = AMediaExtractor_getFileFormat(testExtractor);
+ if (refFormat && testFormat) {
+ const char *refMime = nullptr, *testMime = nullptr;
+ bool hasRefKey = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
+ bool hasTestKey = AMediaFormat_getString(testFormat, AMEDIAFORMAT_KEY_MIME, &testMime);
+ /* TODO: Not Sure if we need to verify any other parameter of file format */
+ if (hasRefKey && hasTestKey && strcmp(refMime, testMime) == 0) {
+ result = true;
+ } else {
+ ALOGE("file format exp/got : %s/%s", refMime, testMime);
+ }
+ }
+ if (refFormat) AMediaFormat_delete(refFormat);
+ if (testFormat) AMediaFormat_delete(testFormat);
+ }
+ return result;
+}
+
+static bool isSeekOk(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor) {
+ const long maxEstDuration = 14000000;
+ const int MAX_SEEK_POINTS = 7;
+ std::srand(kSeed);
+ AMediaCodecBufferInfo refSampleInfo, testSampleInfo;
+ bool result = true;
+ for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(refExtractor); trackID++) {
+ AMediaExtractor_selectTrack(refExtractor, trackID);
+ AMediaExtractor_selectTrack(testExtractor, trackID);
+ for (int i = 0; i < MAX_SEEK_POINTS && result; i++) {
+ double r = ((double)rand() / (RAND_MAX));
+ long pts = (long)(r * maxEstDuration);
+ for (int mode = AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC;
+ mode <= AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC; mode++) {
+ AMediaExtractor_seekTo(refExtractor, pts, (SeekMode)mode);
+ AMediaExtractor_seekTo(testExtractor, pts, (SeekMode)mode);
+ setSampleInfo(refExtractor, &refSampleInfo);
+ setSampleInfo(testExtractor, &testSampleInfo);
+ result = isSampleInfoIdentical(&refSampleInfo, &testSampleInfo);
+ if (!result) {
+ ALOGE(" flags exp/got: %d / %d", refSampleInfo.flags, testSampleInfo.flags);
+ ALOGE(" size exp/got: %d / %d ", refSampleInfo.size, testSampleInfo.size);
+ ALOGE(" ts exp/got: %d / %d ", (int)refSampleInfo.presentationTimeUs,
+ (int)testSampleInfo.presentationTimeUs);
+ }
+ int refTrackIdx = AMediaExtractor_getSampleTrackIndex(refExtractor);
+ int testTrackIdx = AMediaExtractor_getSampleTrackIndex(testExtractor);
+ if (refTrackIdx != testTrackIdx) {
+ ALOGE("trackIdx exp/got: %d/%d ", refTrackIdx, testTrackIdx);
+ result = false;
+ }
+ }
+ }
+ AMediaExtractor_unselectTrack(refExtractor, trackID);
+ AMediaExtractor_unselectTrack(testExtractor, trackID);
+ }
+ return result;
+}
+
+static jboolean nativeTestExtract(JNIEnv* env, jobject, jstring jsrcPath, jstring jrefPath,
+ jstring jmime) {
+ bool isPass = false;
+ const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+ const char* ctestPath = env->GetStringUTFChars(jrefPath, nullptr);
+ const char* cmime = env->GetStringUTFChars(jmime, nullptr);
+ FILE* srcFp = fopen(csrcPath, "rbe");
+ AMediaExtractor* srcExtractor = createExtractorFromFD(srcFp);
+ FILE* testFp = fopen(ctestPath, "rbe");
+ AMediaExtractor* testExtractor = createExtractorFromFD(testFp);
+ if (srcExtractor && testExtractor) {
+ isPass = isMediaSimilar(srcExtractor, testExtractor, cmime);
+ if (!isPass) {
+ ALOGE(" Src and test are different from extractor perspective");
+ }
+ AMediaExtractor_delete(srcExtractor);
+ AMediaExtractor_delete(testExtractor);
+ }
+ if (srcFp) fclose(srcFp);
+ if (testFp) fclose(testFp);
+ env->ReleaseStringUTFChars(jmime, cmime);
+ env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+ env->ReleaseStringUTFChars(jrefPath, ctestPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSeek(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) {
+ bool isPass = false;
+ const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+ const char* cmime = env->GetStringUTFChars(jmime, nullptr);
+ std::vector<SeekTestParams*> seekTestArgs = generateSeekTestArgs(csrcPath, cmime, false);
+ if (!seekTestArgs.empty()) {
+ std::shuffle(seekTestArgs.begin(), seekTestArgs.end(), std::default_random_engine(kSeed));
+ int seekAccErrCnt = checkSeekPoints(csrcPath, cmime, seekTestArgs);
+ if (seekAccErrCnt != 0) {
+ ALOGE("For %s seek chose inaccurate Sync point in: %d / %d", csrcPath, seekAccErrCnt,
+ (int)seekTestArgs.size());
+ isPass = false;
+ } else {
+ isPass = true;
+ }
+ for (auto seekTestArg : seekTestArgs) {
+ delete seekTestArg;
+ }
+ seekTestArgs.clear();
+ } else {
+ ALOGE("No sync samples found.");
+ }
+ env->ReleaseStringUTFChars(jmime, cmime);
+ env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSeekFlakiness(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) {
+ bool isPass = false;
+ const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+ const char* cmime = env->GetStringUTFChars(jmime, nullptr);
+ std::vector<SeekTestParams*> seekTestArgs = generateSeekTestArgs(csrcPath, cmime, true);
+ if (!seekTestArgs.empty()) {
+ std::shuffle(seekTestArgs.begin(), seekTestArgs.end(), std::default_random_engine(kSeed));
+ int flakyErrCnt = checkSeekPoints(csrcPath, cmime, seekTestArgs);
+ if (flakyErrCnt != 0) {
+ ALOGE("No. of Samples where seek showed flakiness is: %d", flakyErrCnt);
+ isPass = false;
+ } else {
+ isPass = true;
+ }
+ for (auto seekTestArg : seekTestArgs) {
+ delete seekTestArg;
+ }
+ seekTestArgs.clear();
+ } else {
+ ALOGE("No sync samples found.");
+ }
+ env->ReleaseStringUTFChars(jmime, cmime);
+ env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSeekToZero(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) {
+ bool isPass = false;
+ const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+ const char* cmime = env->GetStringUTFChars(jmime, nullptr);
+ FILE* srcFp = fopen(csrcPath, "rbe");
+ AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+ if (extractor) {
+ AMediaCodecBufferInfo sampleInfoAtZero;
+ AMediaCodecBufferInfo currInfo;
+ static long randomPts = 1 << 20;
+ for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
+ AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
+ if (format) {
+ const char* currMime = nullptr;
+ bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
+ if (!hasKey || strcmp(currMime, cmime) != 0) {
+ AMediaFormat_delete(format);
+ continue;
+ }
+ AMediaExtractor_selectTrack(extractor, trackID);
+ setSampleInfo(extractor, &sampleInfoAtZero);
+ AMediaExtractor_seekTo(extractor, randomPts, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC);
+ AMediaExtractor_seekTo(extractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
+ setSampleInfo(extractor, &currInfo);
+ isPass = isSampleInfoIdentical(&sampleInfoAtZero, &currInfo);
+ if (!isPass) {
+ ALOGE("seen mismatch seekTo(0, SEEK_TO_CLOSEST_SYNC)");
+ ALOGE(" flags exp/got: %d / %d", sampleInfoAtZero.flags, currInfo.flags);
+ ALOGE(" size exp/got: %d / %d ", sampleInfoAtZero.size, currInfo.size);
+ ALOGE(" ts exp/got: %d / %d ", (int)sampleInfoAtZero.presentationTimeUs,
+ (int)currInfo.presentationTimeUs);
+ AMediaFormat_delete(format);
+ break;
+ }
+ AMediaExtractor_seekTo(extractor, -1L, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
+ setSampleInfo(extractor, &currInfo);
+ isPass = isSampleInfoIdentical(&sampleInfoAtZero, &currInfo);
+ if (!isPass) {
+ ALOGE("seen mismatch seekTo(-1, SEEK_TO_CLOSEST_SYNC)");
+ ALOGE(" flags exp/got: %d / %d", sampleInfoAtZero.flags, currInfo.flags);
+ ALOGE(" size exp/got: %d / %d ", sampleInfoAtZero.size, currInfo.size);
+ ALOGE(" ts exp/got: %d / %d ", (int)sampleInfoAtZero.presentationTimeUs,
+ (int)currInfo.presentationTimeUs);
+ AMediaFormat_delete(format);
+ break;
+ }
+ AMediaExtractor_unselectTrack(extractor, trackID);
+ AMediaFormat_delete(format);
+ }
+ }
+ AMediaExtractor_delete(extractor);
+ }
+ if (srcFp) fclose(srcFp);
+ env->ReleaseStringUTFChars(jmime, cmime);
+ env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestFileFormat(JNIEnv* env, jobject, jstring jsrcPath) {
+ bool isPass = false;
+ const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+ FILE* srcFp = fopen(csrcPath, "rbe");
+ AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+ if (extractor) {
+ AMediaFormat* format = AMediaExtractor_getFileFormat(extractor);
+ const char* mime = nullptr;
+ bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
+ /* TODO: Not Sure if we need to verify any other parameter of file format */
+ if (hasKey && mime && strlen(mime) > 0) {
+ isPass = true;
+ }
+ AMediaFormat_delete(format);
+ AMediaExtractor_delete(extractor);
+ }
+ if (srcFp) fclose(srcFp);
+ env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestDataSource(JNIEnv* env, jobject, jstring jsrcPath, jstring jsrcUrl) {
+ bool isPass = true;
+ const char* csrcUrl = env->GetStringUTFChars(jsrcUrl, nullptr);
+ AMediaExtractor* refExtractor = AMediaExtractor_new();
+ media_status_t status = AMediaExtractor_setDataSource(refExtractor, csrcUrl);
+ if (status == AMEDIA_OK) {
+ isPass &= validateCachedDuration(refExtractor, true);
+ const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+ AMediaDataSource* dataSource = AMediaDataSource_newUri(csrcUrl, 0, nullptr);
+ AMediaExtractor* testExtractor = AMediaExtractor_new();
+ status = AMediaExtractor_setDataSourceCustom(testExtractor, dataSource);
+ if (status != AMEDIA_OK) {
+ ALOGE("setDataSourceCustom failed");
+ isPass = false;
+ } else {
+ isPass &= validateCachedDuration(testExtractor, true);
+ if (!(isMediaSimilar(refExtractor, testExtractor, nullptr) &&
+ isFileFormatIdentical(refExtractor, testExtractor) &&
+ isSeekOk(refExtractor, testExtractor))) {
+ isPass = false;
+ }
+ }
+ if (testExtractor) AMediaExtractor_delete(testExtractor);
+ if (dataSource) AMediaDataSource_delete(dataSource);
+
+ FILE* testFp = fopen(csrcPath, "rbe");
+ testExtractor = createExtractorFromFD(testFp);
+ if (testExtractor == nullptr) {
+ ALOGE("createExtractorFromFD failed for test extractor");
+ isPass = false;
+ } else {
+ isPass &= validateCachedDuration(testExtractor, false);
+ if (!(isMediaSimilar(refExtractor, testExtractor, nullptr) &&
+ isFileFormatIdentical(refExtractor, testExtractor) &&
+ isSeekOk(refExtractor, testExtractor))) {
+ isPass = false;
+ }
+ }
+ if (testExtractor) AMediaExtractor_delete(testExtractor);
+ if (testFp) fclose(testFp);
+ env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+ } else {
+ ALOGE("setDataSource failed");
+ isPass = false;
+ }
+ if (refExtractor) AMediaExtractor_delete(refExtractor);
+ env->ReleaseStringUTFChars(jsrcUrl, csrcUrl);
+ return static_cast<jboolean>(isPass);
+}
+
+int registerAndroidMediaV2CtsExtractorTestSetDS(JNIEnv* env) {
+ const JNINativeMethod methodTable[] = {
+ {"nativeTestDataSource", "(Ljava/lang/String;Ljava/lang/String;)Z",
+ (void*)nativeTestDataSource},
+ };
+ jclass c = env->FindClass("android/mediav2/cts/ExtractorTest$SetDataSourceTest");
+ return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
+
+int registerAndroidMediaV2CtsExtractorTestFunc(JNIEnv* env) {
+ const JNINativeMethod methodTable[] = {
+ {"nativeTestExtract", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+ (void*)nativeTestExtract},
+ {"nativeTestSeek", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)nativeTestSeek},
+ {"nativeTestSeekFlakiness", "(Ljava/lang/String;Ljava/lang/String;)Z",
+ (void*)nativeTestSeekFlakiness},
+ {"nativeTestSeekToZero", "(Ljava/lang/String;Ljava/lang/String;)Z",
+ (void*)nativeTestSeekToZero},
+ {"nativeTestFileFormat", "(Ljava/lang/String;)Z", (void*)nativeTestFileFormat},
+ };
+ jclass c = env->FindClass("android/mediav2/cts/ExtractorTest$FunctionalityTest");
+ return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
+
+extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
+ if (registerAndroidMediaV2CtsExtractorTestSetDS(env) != JNI_OK) return JNI_ERR;
+ if (registerAndroidMediaV2CtsExtractorTestFunc(env) != JNI_OK) return JNI_ERR;
+ return JNI_VERSION_1_6;
+}
\ No newline at end of file
diff --git a/tests/media/jni/NativeMuxerTest.cpp b/tests/media/jni/NativeMuxerTest.cpp
index f9adbd7..62d1535 100644
--- a/tests/media/jni/NativeMuxerTest.cpp
+++ b/tests/media/jni/NativeMuxerTest.cpp
@@ -731,6 +731,8 @@
return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
}
+extern int registerAndroidMediaV2CtsMuxerUnitTestApi(JNIEnv* env);
+
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
@@ -738,5 +740,6 @@
if (registerAndroidMediaV2CtsMuxerTestMultiTrack(env) != JNI_OK) return JNI_ERR;
if (registerAndroidMediaV2CtsMuxerTestOffsetPts(env) != JNI_OK) return JNI_ERR;
if (registerAndroidMediaV2CtsMuxerTestSimpleMux(env) != JNI_OK) return JNI_ERR;
+ if (registerAndroidMediaV2CtsMuxerUnitTestApi(env) != JNI_OK) return JNI_ERR;
return JNI_VERSION_1_6;
}
\ No newline at end of file
diff --git a/tests/media/jni/NativeMuxerUnitTest.cpp b/tests/media/jni/NativeMuxerUnitTest.cpp
new file mode 100644
index 0000000..e267799
--- /dev/null
+++ b/tests/media/jni/NativeMuxerUnitTest.cpp
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2019 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 "NativeMuxerUnitTest"
+#include <log/log.h>
+#include <fcntl.h>
+#include <jni.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <cmath>
+#include <cstring>
+#include <fstream>
+#include <map>
+#include <vector>
+
+#include "NativeMediaConstants.h"
+#include "media/NdkMediaExtractor.h"
+#include "media/NdkMediaFormat.h"
+#include "media/NdkMediaMuxer.h"
+
+static media_status_t insertPerFrameSubtitles(AMediaMuxer* muxer, long pts, size_t trackID) {
+ const char* greeting = "hello world";
+ auto* info = new AMediaCodecBufferInfo;
+ info->offset = 0;
+ info->size = strlen(greeting);
+ info->presentationTimeUs = pts;
+ info->flags = 0;
+ media_status_t status = AMediaMuxer_writeSampleData(muxer, trackID, (uint8_t*)greeting, info);
+ delete info;
+ return status;
+}
+
+static jboolean nativeTestIfInvalidFdIsRejected(JNIEnv*, jobject) {
+ AMediaMuxer* muxer = AMediaMuxer_new(-1, (OutputFormat)OUTPUT_FORMAT_THREE_GPP);
+ bool isPass = true;
+ if (muxer != nullptr) {
+ AMediaMuxer_delete(muxer);
+ ALOGE("error: muxer constructor accepts invalid file descriptor");
+ isPass = false;
+ }
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfReadOnlyFdIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "rbe");
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_THREE_GPP);
+ bool isPass = true;
+ if (muxer != nullptr) {
+ AMediaMuxer_delete(muxer);
+ ALOGE("error: muxer constructor accepts read-only file descriptor");
+ isPass = false;
+ }
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteOnlyFdIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe");
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_WEBM);
+ bool isPass = true;
+ if (muxer != nullptr) {
+ AMediaMuxer_delete(muxer);
+ ALOGE("error: muxer constructor accepts write-only file descriptor");
+ isPass = false;
+ }
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfNonSeekableFdIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ mkfifo(cdstPath, 0666);
+ int fd = open(cdstPath, O_WRONLY);
+ AMediaMuxer* muxer = AMediaMuxer_new(fd, (OutputFormat)OUTPUT_FORMAT_THREE_GPP);
+ bool isPass = true;
+ if (muxer != nullptr) {
+ AMediaMuxer_delete(muxer);
+ ALOGE("error: muxer constructor accepts non-seekable file descriptor");
+ isPass = false;
+ }
+ close(fd);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfInvalidOutputFormatIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)(OUTPUT_FORMAT_LIST_END + 1));
+ bool isPass = true;
+ if (muxer != nullptr) {
+ AMediaMuxer_delete(muxer);
+ ALOGE("error: muxer constructor accepts invalid output format");
+ isPass = false;
+ }
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfInvalidMediaFormatIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ bool isPass = true;
+ if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+ ALOGE("error: muxer.addTrack succeeds with format that has no mime key");
+ isPass = false;
+ }
+
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "text/cea-608");
+ if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+ ALOGE("error: muxer.addTrack succeeds with format whose mime is non-compliant");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfCorruptMediaFormatIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_AUDIO_AAC);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, -1);
+ if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+ ALOGE("error: muxer.addTrack succeeds with erroneous key-value pairs in media format");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfAddTrackSucceedsAfterStart(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ isPass &= AMediaMuxer_addTrack(muxer, format) >= 0;
+ isPass &= (AMediaMuxer_start(muxer) == AMEDIA_OK);
+ if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+ ALOGE("error: muxer.addTrack succeeds after muxer.start");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfAddTrackSucceedsAfterWriteSampleData(JNIEnv* env, jobject,
+ jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+ if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+ ALOGE("error: muxer.addTrack succeeds after muxer.writeSampleData");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfAddTrackSucceedsAfterStop(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+ isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+ if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+ ALOGE("error: muxer.addTrack succeeds after muxer.stop");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfMuxerStartsBeforeAddTrack(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ if (AMediaMuxer_start(muxer) == AMEDIA_OK) {
+ ALOGE("error: muxer.start succeeds before muxer.addTrack");
+ isPass = false;
+ }
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIdempotentStart(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ isPass &= AMediaMuxer_addTrack(muxer, format) >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ if (AMediaMuxer_start(muxer) == AMEDIA_OK) {
+ ALOGE("error: double muxer.start succeeds");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfMuxerStartsAfterWriteSampleData(JNIEnv* env, jobject,
+ jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+ if (AMediaMuxer_start(muxer) == AMEDIA_OK) {
+ ALOGE("error: muxer.start succeeds after muxer.writeSampleData");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfMuxerStartsAfterStop(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+ isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+ if (AMediaMuxer_start(muxer) == AMEDIA_OK) {
+ ALOGE("error: muxer.start succeeds after muxer.stop");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestStopOnANonStartedMuxer(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ isPass &= AMediaMuxer_addTrack(muxer, format) >= 0;
+ if (AMEDIA_OK == AMediaMuxer_stop(muxer)) {
+ ALOGE("error: muxer.stop succeeds before muxer.start");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIdempotentStop(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+ isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+ if (AMEDIA_OK == AMediaMuxer_stop(muxer)) {
+ ALOGE("error: double muxer.stop succeeds");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSimpleStartStop(JNIEnv* env, jobject, jstring jdstPath) {
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ bool isPass = true;
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteSampleDataRejectsInvalidTrackIndex(JNIEnv* env, jobject,
+ jstring jdstPath) {
+ bool isPass = true;
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+ if (AMEDIA_OK == insertPerFrameSubtitles(muxer, 22000, trackID + 1)) {
+ ALOGE("error: muxer.writeSampleData succeeds for invalid track ID");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteSampleDataRejectsInvalidPts(JNIEnv* env, jobject,
+ jstring jdstPath) {
+ bool isPass = true;
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+ if (AMEDIA_OK == insertPerFrameSubtitles(muxer, -33000, trackID)) {
+ ALOGE("error: muxer.writeSampleData succeeds for invalid pts");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteSampleDataSucceedsBeforeStart(JNIEnv* env, jobject,
+ jstring jdstPath) {
+ bool isPass = true;
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ if (AMEDIA_OK == insertPerFrameSubtitles(muxer, 0, trackID)) {
+ ALOGE("error: muxer.writeSampleData succeeds before muxer.start");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteSampleDataSucceedsAfterStop(JNIEnv* env, jobject,
+ jstring jdstPath) {
+ bool isPass = true;
+ const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+ FILE* ofp = fopen(cdstPath, "wbe+");
+ AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+ ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+ isPass &= trackID >= 0;
+ isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+ isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+ isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+ if (AMEDIA_OK == insertPerFrameSubtitles(muxer, 33000, trackID)) {
+ ALOGE("error: muxer.writeSampleData succeeds after muxer.stop");
+ isPass = false;
+ }
+ AMediaFormat_delete(format);
+ AMediaMuxer_delete(muxer);
+ fclose(ofp);
+ env->ReleaseStringUTFChars(jdstPath, cdstPath);
+ return static_cast<jboolean>(isPass);
+}
+
+int registerAndroidMediaV2CtsMuxerUnitTestApi(JNIEnv* env) {
+ const JNINativeMethod methodTable[] = {
+ {"nativeTestIfInvalidFdIsRejected", "()Z", (void*)nativeTestIfInvalidFdIsRejected},
+ {"nativeTestIfReadOnlyFdIsRejected", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfReadOnlyFdIsRejected},
+ {"nativeTestIfWriteOnlyFdIsRejected", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfWriteOnlyFdIsRejected},
+ {"nativeTestIfNonSeekableFdIsRejected", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfNonSeekableFdIsRejected},
+ {"nativeTestIfInvalidOutputFormatIsRejected", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfInvalidOutputFormatIsRejected},
+
+ {"nativeTestIfInvalidMediaFormatIsRejected", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfInvalidMediaFormatIsRejected},
+ {"nativeTestIfCorruptMediaFormatIsRejected", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfCorruptMediaFormatIsRejected},
+ {"nativeTestIfAddTrackSucceedsAfterStart", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfAddTrackSucceedsAfterStart},
+ {"nativeTestIfAddTrackSucceedsAfterWriteSampleData", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfAddTrackSucceedsAfterWriteSampleData},
+ {"nativeTestIfAddTrackSucceedsAfterStop", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfAddTrackSucceedsAfterStop},
+
+ {"nativeTestIfMuxerStartsBeforeAddTrack", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfMuxerStartsBeforeAddTrack},
+ {"nativeTestIdempotentStart", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIdempotentStart},
+ {"nativeTestIfMuxerStartsAfterWriteSampleData", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfMuxerStartsAfterWriteSampleData},
+ {"nativeTestIfMuxerStartsAfterStop", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfMuxerStartsAfterStop},
+
+ {"nativeTestStopOnANonStartedMuxer", "(Ljava/lang/String;)Z",
+ (void*)nativeTestStopOnANonStartedMuxer},
+ {"nativeTestIdempotentStop", "(Ljava/lang/String;)Z", (void*)nativeTestIdempotentStop},
+ {"nativeTestSimpleStartStop", "(Ljava/lang/String;)Z",
+ (void*)nativeTestSimpleStartStop},
+
+ {"nativeTestIfWriteSampleDataRejectsInvalidTrackIndex", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfWriteSampleDataRejectsInvalidTrackIndex},
+ {"nativeTestIfWriteSampleDataRejectsInvalidPts", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfWriteSampleDataRejectsInvalidPts},
+ {"nativeTestIfWriteSampleDataSucceedsBeforeStart", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfWriteSampleDataSucceedsBeforeStart},
+ {"nativeTestIfWriteSampleDataSucceedsAfterStop", "(Ljava/lang/String;)Z",
+ (void*)nativeTestIfWriteSampleDataSucceedsAfterStop},
+ };
+ jclass c = env->FindClass("android/mediav2/cts/MuxerUnitTest$TestApiNative");
+ return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
new file mode 100644
index 0000000..19fe20a
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
@@ -0,0 +1,939 @@
+/*
+ * Copyright (C) 2019 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.mediav2.cts;
+
+import android.content.pm.PackageManager;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Validate decode functionality of listed decoder components
+ */
+@RunWith(Parameterized.class)
+public class CodecDecoderTest extends CodecTestBase {
+ private static final String LOG_TAG = CodecDecoderTest.class.getSimpleName();
+
+ private final String mMime;
+ private final String mTestFile;
+ private final String mRefFile;
+ private final String mReconfigFile;
+ private final float mRmsError;
+
+ private ArrayList<ByteBuffer> mCsdBuffers;
+ private int mCurrCsdIdx;
+
+ private MediaExtractor mExtractor;
+
+ public CodecDecoderTest(String mime, String testFile, String refFile, String reconfigFile,
+ float rmsError) {
+ mMime = mime;
+ mTestFile = testFile;
+ mRefFile = refFile;
+ mReconfigFile = reconfigFile;
+ mRmsError = rmsError;
+ mAsyncHandle = new CodecAsyncHandler();
+ mCsdBuffers = new ArrayList<>();
+ mIsAudio = mMime.startsWith("audio/");
+ }
+
+ private static boolean isTv() {
+ return InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ }
+
+ private static boolean shouldRunTest(String mime) {
+ return !mime.equals(MediaFormat.MIMETYPE_VIDEO_AV1) &&
+ (!mime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG2) || isTv());
+ }
+
+ private short[] setUpReference() throws IOException {
+ File refFile = new File(mInpPrefix + mRefFile);
+ short[] refData;
+ try (FileInputStream refStream = new FileInputStream(refFile)) {
+ FileChannel fileChannel = refStream.getChannel();
+ int length = (int) refFile.length();
+ ByteBuffer refBuffer = ByteBuffer.allocate(length);
+ refBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ fileChannel.read(refBuffer);
+ refData = new short[length / 2];
+ refBuffer.position(0);
+ for (int i = 0; i < length / 2; i++) {
+ refData[i] = refBuffer.getShort();
+ }
+ }
+ return refData;
+ }
+
+ private MediaFormat setUpSource(String srcFile) throws IOException {
+ mExtractor = new MediaExtractor();
+ mExtractor.setDataSource(mInpPrefix + srcFile);
+ for (int trackID = 0; trackID < mExtractor.getTrackCount(); trackID++) {
+ MediaFormat format = mExtractor.getTrackFormat(trackID);
+ if (mMime.equalsIgnoreCase(format.getString(MediaFormat.KEY_MIME))) {
+ mExtractor.selectTrack(trackID);
+ return mExtractor.getTrackFormat(trackID);
+ }
+ }
+ fail("No track with mime: " + mMime + " found in file: " + srcFile);
+ return null;
+ }
+
+ private boolean hasCSD(MediaFormat format) {
+ return format.containsKey("csd-0");
+ }
+
+ private void enqueueCodecConfig(int bufferIndex) {
+ ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
+ ByteBuffer csdBuffer = mCsdBuffers.get(mCurrCsdIdx);
+ inputBuffer.put((ByteBuffer) csdBuffer.rewind());
+ mCodec.queueInputBuffer(bufferIndex, 0, csdBuffer.limit(), 0,
+ MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
+ if (ENABLE_LOGS) {
+ Log.v(LOG_TAG, "queued csd: id: " + bufferIndex + " size: " + csdBuffer.limit());
+ }
+ }
+
+ void enqueueInput(int bufferIndex) {
+ if (mExtractor.getSampleSize() < 0) {
+ enqueueEOS(bufferIndex);
+ } else {
+ ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
+ mExtractor.readSampleData(inputBuffer, 0);
+ int size = (int) mExtractor.getSampleSize();
+ long pts = mExtractor.getSampleTime();
+ int extractorFlags = mExtractor.getSampleFlags();
+ int codecFlags = 0;
+ if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
+ codecFlags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
+ }
+ if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) {
+ codecFlags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME;
+ }
+ if (!mExtractor.advance() && mSignalEOSWithLastFrame) {
+ codecFlags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+ mSawInputEOS = true;
+ }
+ if (ENABLE_LOGS) {
+ Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts +
+ " flags: " + codecFlags);
+ }
+ mCodec.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags);
+ if (size > 0 && (codecFlags & (MediaCodec.BUFFER_FLAG_CODEC_CONFIG |
+ MediaCodec.BUFFER_FLAG_PARTIAL_FRAME)) == 0) {
+ mInputCount++;
+ }
+ }
+ }
+
+ private void enqueueInput(int bufferIndex, ByteBuffer buffer, MediaCodec.BufferInfo info) {
+ ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
+ inputBuffer.put((ByteBuffer) buffer.rewind());
+ int flags = 0;
+ if ((info.flags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
+ flags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
+ }
+ if ((info.flags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) {
+ flags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME;
+ }
+ if (ENABLE_LOGS) {
+ Log.v(LOG_TAG, "input: id: " + bufferIndex + " flags: " + info.flags + " size: " +
+ info.size + " timestamp: " + info.presentationTimeUs);
+ }
+ mCodec.queueInputBuffer(bufferIndex, info.offset, info.size, info.presentationTimeUs,
+ flags);
+ if (info.size > 0 && ((flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) &&
+ ((flags & MediaCodec.BUFFER_FLAG_PARTIAL_FRAME) == 0)) {
+ mInputCount++;
+ }
+ }
+
+ void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+ if (info.size > 0 && mSaveToMem) {
+ if (mIsAudio) {
+ ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
+ mOutputBuff.saveToMemory(buf, info);
+ } else {
+ Image img = mCodec.getOutputImage(bufferIndex);
+ if (img != null) {
+ mOutputBuff.checksum(img);
+ } else {
+ ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
+ mOutputBuff.checksum(buf, info.size);
+ }
+ }
+ }
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ mSawOutputEOS = true;
+ }
+ if (ENABLE_LOGS) {
+ Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " +
+ info.size + " timestamp: " + info.presentationTimeUs);
+ }
+ if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+ if (info.presentationTimeUs <= mPrevOutputPts) {
+ fail("Timestamp ordering check failed: last timestamp: " + mPrevOutputPts +
+ " current timestamp:" + info.presentationTimeUs);
+ }
+ mPrevOutputPts = info.presentationTimeUs;
+ mOutputCount++;
+ }
+ mCodec.releaseOutputBuffer(bufferIndex, false);
+ }
+
+ private void doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list)
+ throws InterruptedException {
+ int frameCount = 0;
+ if (mIsCodecInAsyncMode) {
+ // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
+ while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < list.size()) {
+ Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
+ if (element != null) {
+ int bufferID = element.first;
+ MediaCodec.BufferInfo info = element.second;
+ if (info != null) {
+ dequeueOutput(bufferID, info);
+ } else {
+ enqueueInput(bufferID, buffer, list.get(frameCount));
+ frameCount++;
+ }
+ }
+ }
+ } else {
+ MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+ // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
+ while (!mSawInputEOS && frameCount < list.size()) {
+ int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+ if (outputBufferId >= 0) {
+ dequeueOutput(outputBufferId, outInfo);
+ } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ mOutFormat = mCodec.getOutputFormat();
+ mSignalledOutFormatChanged = true;
+ }
+ int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
+ if (inputBufferId != -1) {
+ enqueueInput(inputBufferId, buffer, list.get(frameCount));
+ frameCount++;
+ }
+ }
+ }
+ }
+
+ private ArrayList<MediaCodec.BufferInfo> createSubFrames(ByteBuffer buffer, int sfCount) {
+ int size = (int) mExtractor.getSampleSize();
+ if (size < 0) return null;
+ mExtractor.readSampleData(buffer, 0);
+ long pts = mExtractor.getSampleTime();
+ int flags = mExtractor.getSampleFlags();
+ if (size < sfCount) sfCount = size;
+ ArrayList<MediaCodec.BufferInfo> list = new ArrayList<>();
+ int offset = 0;
+ for (int i = 0; i < sfCount; i++) {
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ info.offset = offset;
+ info.presentationTimeUs = pts;
+ info.flags = flags;
+ if (i != sfCount - 1) {
+ info.size = size / sfCount;
+ info.flags |= MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME;
+ } else {
+ info.size = size - offset;
+ }
+ list.add(info);
+ offset += info.size;
+ }
+ return list;
+ }
+
+ private void queueCodecConfig() throws InterruptedException {
+ if (mIsCodecInAsyncMode) {
+ for (mCurrCsdIdx = 0; !mAsyncHandle.hasSeenError() && mCurrCsdIdx < mCsdBuffers.size();
+ mCurrCsdIdx++) {
+ Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getInput();
+ if (element != null) {
+ enqueueCodecConfig(element.first);
+ }
+ }
+ } else {
+ for (mCurrCsdIdx = 0; mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) {
+ enqueueCodecConfig(mCodec.dequeueInputBuffer(-1));
+ }
+ }
+ }
+
+ private void decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit)
+ throws IOException, InterruptedException {
+ mSaveToMem = true;
+ mOutputBuff = new OutputManager();
+ mCodec = MediaCodec.createByCodecName(decoder);
+ MediaFormat format = setUpSource(file);
+ configureCodec(format, false, true, false);
+ mCodec.start();
+ mExtractor.seekTo(pts, mode);
+ doWork(frameLimit);
+ queueEOS();
+ waitForAllOutputs();
+ mCodec.stop();
+ mCodec.release();
+ mExtractor.release();
+ mSaveToMem = false;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> input() {
+ return Arrays.asList(new Object[][]{
+ {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_1ch_8kHz_lame_cbr.mp3",
+ "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_lame_vbr.mp3",
+ 91.022f * 1.05f},
+ {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_1ch_16kHz_lame_vbr.mp3",
+ "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_lame_vbr.mp3",
+ 119.256f * 1.05f},
+ {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_2ch_44kHz_lame_cbr.mp3",
+ "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_lame_vbr.mp3",
+ 103.60f * 1.05f},
+ {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_2ch_44kHz_lame_vbr.mp3",
+ "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_8kHz_lame_cbr.mp3",
+ 53.066f * 1.05f},
+ {MediaFormat.MIMETYPE_AUDIO_AMR_WB, "bbb_1ch_16kHz_16kbps_amrwb.3gp",
+ "bbb_1ch_16kHz_s16le.raw", "bbb_1ch_16kHz_23kbps_amrwb.3gp",
+ 2393.598f * 1.05f},
+ {MediaFormat.MIMETYPE_AUDIO_AMR_NB, "bbb_1ch_8kHz_10kbps_amrnb.3gp",
+ "bbb_1ch_8kHz_s16le.raw", "bbb_1ch_8kHz_8kbps_amrnb.3gp", -1.0f},
+ {MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_1ch_16kHz_flac.mka",
+ "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_flac.mka", 0.0f},
+ {MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_2ch_44kHz_flac.mka",
+ "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_flac.mka", 0.0f},
+ {MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_1ch_16kHz.wav", "bbb_1ch_16kHz_s16le.raw",
+ "bbb_2ch_44kHz.wav", 0.0f},
+ {MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_2ch_44kHz.wav", "bbb_2ch_44kHz_s16le.raw",
+ "bbb_1ch_16kHz.wav", 0.0f},
+ {MediaFormat.MIMETYPE_AUDIO_VORBIS, "bbb_1ch_16kHz_vorbis.mka",
+ "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_vorbis.mka", -1.0f},
+ {MediaFormat.MIMETYPE_AUDIO_OPUS, "bbb_2ch_48kHz_opus.mka",
+ "bbb_2ch_48kHz_s16le.raw", "bbb_1ch_48kHz_opus.mka", -1.0f},
+ {MediaFormat.MIMETYPE_AUDIO_AAC, "bbb_1ch_16kHz_aac.mp4",
+ "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_aac.mp4", -1.0f},
+ {MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_340x280_768kbps_30fps_mpeg2.mp4", null,
+ "bbb_520x390_1mbps_30fps_mpeg2.mp4", -1.0f},
+ {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_340x280_768kbps_30fps_avc.mp4", null,
+ "bbb_520x390_1mbps_30fps_avc.mp4", -1.0f},
+ {MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_520x390_1mbps_30fps_hevc.mp4", null,
+ "bbb_340x280_768kbps_30fps_hevc.mp4", -1.0f},
+ {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_128x96_64kbps_12fps_mpeg4.mp4",
+ null, "bbb_176x144_192kbps_15fps_mpeg4.mp4", -1.0f},
+ {MediaFormat.MIMETYPE_VIDEO_H263, "bbb_176x144_128kbps_15fps_h263.3gp",
+ null, "bbb_176x144_192kbps_10fps_h263.3gp", -1.0f},
+ {MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_340x280_768kbps_30fps_vp8.webm", null,
+ "bbb_520x390_1mbps_30fps_vp8.webm", -1.0f},
+ {MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_340x280_768kbps_30fps_vp9.webm", null,
+ "bbb_520x390_1mbps_30fps_vp9.webm", -1.0f},
+ {MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_340x280_768kbps_30fps_av1.mp4", null,
+ "bbb_520x390_1mbps_30fps_av1.mp4", -1.0f},
+ });
+ }
+
+ /**
+ * Tests decoder for combinations:
+ * 1. Codec Sync Mode, Signal Eos with Last frame
+ * 2. Codec Sync Mode, Signal Eos Separately
+ * 3. Codec Async Mode, Signal Eos with Last frame
+ * 4. Codec Async Mode, Signal Eos Separately
+ * In all these scenarios, Timestamp ordering is verified, For audio the Rms of output has to be
+ * within the allowed tolerance. The output has to be consistent (not flaky) in all runs.
+ */
+ @LargeTest
+ @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+ public void testSimpleDecode() throws IOException, InterruptedException {
+ MediaFormat format = setUpSource(mTestFile);
+ if (!mIsAudio) {
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+ }
+ ArrayList<MediaFormat> formats = new ArrayList<>();
+ formats.add(format);
+ ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+ if (listOfDecoders.isEmpty()) {
+ mExtractor.release();
+ if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+ else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+ }
+ boolean[] boolStates = {true, false};
+ mSaveToMem = true;
+ for (String decoder : listOfDecoders) {
+ mCodec = MediaCodec.createByCodecName(decoder);
+ OutputManager ref = mSaveToMem ? new OutputManager() : null;
+ OutputManager test = mSaveToMem ? new OutputManager() : null;
+ int loopCounter = 0;
+ for (boolean eosType : boolStates) {
+ for (boolean isAsync : boolStates) {
+ boolean validateFormat = true;
+ String log = String.format("codec: %s, file: %s, mode: %s, eos type: %s:: ",
+ decoder, mTestFile, (isAsync ? "async" : "sync"),
+ (eosType ? "eos with last frame" : "eos separate"));
+ if (mSaveToMem) {
+ mOutputBuff = loopCounter == 0 ? ref : test;
+ mOutputBuff.reset();
+ }
+ mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ configureCodec(format, isAsync, eosType, false);
+ MediaFormat defFormat = mCodec.getOutputFormat();
+ if (isFormatSimilar(format, defFormat)) {
+ if (ENABLE_LOGS) {
+ Log.d("Input format is same as default for format for %s", decoder);
+ }
+ validateFormat = false;
+ }
+ mCodec.start();
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ if (mSaveToMem && loopCounter != 0) {
+ assertTrue(log + "decoder output is flaky", ref.equals(test));
+ }
+ if (validateFormat) {
+ assertTrue(log + "not received format change",
+ mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+ mSignalledOutFormatChanged);
+ assertTrue(log + "configured format and output format are not similar",
+ isFormatSimilar(format,
+ mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+ mOutFormat));
+ }
+ loopCounter++;
+ }
+ }
+ mCodec.release();
+ if (mSaveToMem && mRefFile != null && mRmsError >= 0) {
+ short[] refData = setUpReference();
+ assertTrue(String.format("%s rms error too high", mTestFile),
+ ref.getRmsError(refData) <= mRmsError);
+ }
+ }
+ mExtractor.release();
+ }
+
+ /**
+ * Tests flush when codec is in sync and async mode. In these scenarios, Timestamp
+ * ordering is verified. The output has to be consistent (not flaky) in all runs
+ */
+ @Ignore("TODO(b/147576107)")
+ @LargeTest
+ @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+ public void testFlush() throws IOException, InterruptedException {
+ MediaFormat format = setUpSource(mTestFile);
+ mExtractor.release();
+ if (!mIsAudio) {
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+ }
+ ArrayList<MediaFormat> formats = new ArrayList<>();
+ formats.add(format);
+ ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+ if (listOfDecoders.isEmpty()) {
+ if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+ else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+ }
+ mCsdBuffers.clear();
+ for (int i = 0; ; i++) {
+ String csdKey = "csd-" + i;
+ if (format.containsKey(csdKey)) {
+ mCsdBuffers.add(format.getByteBuffer(csdKey));
+ } else break;
+ }
+ final long pts = 500000;
+ final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
+ boolean[] boolStates = {true, false};
+ for (String decoder : listOfDecoders) {
+ decodeToMemory(mTestFile, decoder, pts, mode, Integer.MAX_VALUE);
+ OutputManager ref = mOutputBuff;
+ OutputManager test = new OutputManager();
+ mOutputBuff = test;
+ setUpSource(mTestFile);
+ mCodec = MediaCodec.createByCodecName(decoder);
+ for (boolean isAsync : boolStates) {
+ String log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+ mTestFile, (isAsync ? "async" : "sync"));
+ mExtractor.seekTo(0, mode);
+ configureCodec(format, isAsync, true, false);
+ MediaFormat defFormat = mCodec.getOutputFormat();
+ boolean validateFormat = true;
+ if (isFormatSimilar(format, defFormat)) {
+ if (ENABLE_LOGS) {
+ Log.d("Input format is same as default for format for %s", decoder);
+ }
+ validateFormat = false;
+ }
+ mCodec.start();
+
+ /* test flush in running state before queuing input */
+ flushCodec();
+ if (mIsCodecInAsyncMode) mCodec.start();
+ queueCodecConfig(); /* flushed codec too soon after start, resubmit csd */
+
+ doWork(1);
+ flushCodec();
+ if (mIsCodecInAsyncMode) mCodec.start();
+ queueCodecConfig(); /* flushed codec too soon after start, resubmit csd */
+
+ mExtractor.seekTo(0, mode);
+ doWork(23);
+
+ /* test flush in running state */
+ flushCodec();
+ if (mIsCodecInAsyncMode) mCodec.start();
+ mSaveToMem = true;
+ test.reset();
+ mExtractor.seekTo(pts, mode);
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ assertTrue(log + "decoder output is flaky", ref.equals(test));
+
+ /* test flush in eos state */
+ flushCodec();
+ if (mIsCodecInAsyncMode) mCodec.start();
+ test.reset();
+ mExtractor.seekTo(pts, mode);
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ assertTrue(log + "decoder output is flaky", ref.equals(test));
+ if (validateFormat) {
+ assertTrue(log + "not received format change",
+ mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+ mSignalledOutFormatChanged);
+ assertTrue(log + "configured format and output format are not similar",
+ isFormatSimilar(format,
+ mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+ mOutFormat));
+ }
+ mSaveToMem = false;
+ }
+ mCodec.release();
+ mExtractor.release();
+ }
+ }
+
+ /**
+ * Tests reconfigure when codec is in sync and async mode. In these scenarios, Timestamp
+ * ordering is verified. The output has to be consistent (not flaky) in all runs
+ */
+ @Ignore("TODO(b/148523403)")
+ @LargeTest
+ @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+ public void testReconfigure() throws IOException, InterruptedException {
+ MediaFormat format = setUpSource(mTestFile);
+ mExtractor.release();
+ MediaFormat newFormat = setUpSource(mReconfigFile);
+ mExtractor.release();
+ if (!mIsAudio) {
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+ newFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+ }
+ ArrayList<MediaFormat> formats = new ArrayList<>();
+ formats.add(format);
+ formats.add(newFormat);
+ ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+ if (listOfDecoders.isEmpty()) {
+ if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+ else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+ }
+ final long pts = 500000;
+ final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
+ boolean[] boolStates = {true, false};
+ for (String decoder : listOfDecoders) {
+ decodeToMemory(mTestFile, decoder, pts, mode, Integer.MAX_VALUE);
+ OutputManager ref = mOutputBuff;
+ decodeToMemory(mReconfigFile, decoder, pts, mode, Integer.MAX_VALUE);
+ OutputManager configRef = mOutputBuff;
+ OutputManager test = new OutputManager();
+ mOutputBuff = test;
+ mCodec = MediaCodec.createByCodecName(decoder);
+ for (boolean isAsync : boolStates) {
+ setUpSource(mTestFile);
+ String log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+ mTestFile, (isAsync ? "async" : "sync"));
+ mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ configureCodec(format, isAsync, true, false);
+ MediaFormat defFormat = mCodec.getOutputFormat();
+ boolean validateFormat = true;
+ if (isFormatSimilar(format, defFormat)) {
+ if (ENABLE_LOGS) {
+ Log.d("Input format is same as default for format for %s", decoder);
+ }
+ validateFormat = false;
+ }
+
+ /* test reconfigure in stopped state */
+ reConfigureCodec(format, !isAsync, false, false);
+ mCodec.start();
+
+ /* test reconfigure in running state before queuing input */
+ reConfigureCodec(format, !isAsync, false, false);
+ mCodec.start();
+ doWork(23);
+
+ if (validateFormat) {
+ assertTrue(log + "not received format change",
+ mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+ mSignalledOutFormatChanged);
+ assertTrue(log + "configured format and output format are not similar",
+ isFormatSimilar(format,
+ mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+ mOutFormat));
+ }
+
+ /* test reconfigure codec in running state */
+ reConfigureCodec(format, isAsync, true, false);
+ mCodec.start();
+ mSaveToMem = true;
+ test.reset();
+ mExtractor.seekTo(pts, mode);
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ assertTrue(log + "decoder output is flaky", ref.equals(test));
+ if (validateFormat) {
+ assertTrue(log + "not received format change",
+ mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+ mSignalledOutFormatChanged);
+ assertTrue(log + "configured format and output format are not similar",
+ isFormatSimilar(format,
+ mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+ mOutFormat));
+ }
+
+ /* test reconfigure codec at eos state */
+ reConfigureCodec(format, !isAsync, false, false);
+ mCodec.start();
+ test.reset();
+ mExtractor.seekTo(pts, mode);
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ assertTrue(log + "decoder output is flaky", ref.equals(test));
+ if (validateFormat) {
+ assertTrue(log + "not received format change",
+ mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+ mSignalledOutFormatChanged);
+ assertTrue(log + "configured format and output format are not similar",
+ isFormatSimilar(format,
+ mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+ mOutFormat));
+ }
+ mExtractor.release();
+
+ /* test reconfigure codec for new file */
+ setUpSource(mReconfigFile);
+ log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+ mReconfigFile, (isAsync ? "async" : "sync"));
+ reConfigureCodec(newFormat, isAsync, false, false);
+ if (isFormatSimilar(newFormat, defFormat)) {
+ if (ENABLE_LOGS) {
+ Log.d("Input format is same as default for format for %s", decoder);
+ }
+ validateFormat = false;
+ }
+ mCodec.start();
+ test.reset();
+ mExtractor.seekTo(pts, mode);
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ assertTrue(log + "decoder output is flaky", configRef.equals(test));
+ if (validateFormat) {
+ assertTrue(log + "not received format change",
+ mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+ mSignalledOutFormatChanged);
+ assertTrue(log + "configured format and output format are not similar",
+ isFormatSimilar(newFormat,
+ mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+ mOutFormat));
+ }
+ mSaveToMem = false;
+ mExtractor.release();
+ }
+ mCodec.release();
+ }
+ }
+
+ /**
+ * Tests decoder for only EOS frame
+ */
+ @SmallTest
+ @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
+ public void testOnlyEos() throws IOException, InterruptedException {
+ MediaFormat format = setUpSource(mTestFile);
+ if (!mIsAudio) {
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+ }
+ ArrayList<MediaFormat> formats = new ArrayList<>();
+ formats.add(format);
+ ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+ if (listOfDecoders.isEmpty()) {
+ mExtractor.release();
+ if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+ else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+ }
+ boolean[] boolStates = {true, false};
+ for (String decoder : listOfDecoders) {
+ mCodec = MediaCodec.createByCodecName(decoder);
+ for (boolean isAsync : boolStates) {
+ String log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+ mTestFile, (isAsync ? "async" : "sync"));
+ configureCodec(format, isAsync, false, false);
+ mCodec.start();
+ queueEOS();
+ waitForAllOutputs();
+ mCodec.stop();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ }
+ mCodec.release();
+ }
+ mExtractor.release();
+ }
+
+ /**
+ * Test Decoder by Queuing CSD separately
+ */
+ @LargeTest
+ @Ignore("TODO(b/149031058)")
+ @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+ public void testSimpleDecodeQueueCSD() throws IOException, InterruptedException {
+ MediaFormat format = setUpSource(mTestFile);
+ if (!mIsAudio) {
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+ }
+ Assume.assumeTrue("Format has no CSD, ignoring test for mime:" + mMime, hasCSD(format));
+ ArrayList<MediaFormat> formats = new ArrayList<>();
+ formats.add(format);
+ formats.add(new MediaFormat(format));
+ for (int i = 0; ; i++) {
+ String csdKey = "csd-" + i;
+ if (format.containsKey(csdKey)) {
+ mCsdBuffers.add(format.getByteBuffer(csdKey));
+ format.removeKey(csdKey);
+ } else break;
+ }
+ ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+ if (listOfDecoders.isEmpty()) {
+ mExtractor.release();
+ if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+ else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+ }
+ boolean[] boolStates = {true, false};
+ mSaveToMem = true;
+ for (String decoder : listOfDecoders) {
+ mCodec = MediaCodec.createByCodecName(decoder);
+ OutputManager ref = mSaveToMem ? new OutputManager() : null;
+ OutputManager test = mSaveToMem ? new OutputManager() : null;
+ int loopCounter = 0;
+ for (MediaFormat fmt : formats) {
+ for (boolean eosMode : boolStates) {
+ for (boolean isAsync : boolStates) {
+ boolean validateFormat = true;
+ String log = String.format("codec: %s, file: %s, mode: %s, eos type: %s:: ",
+ decoder, mTestFile, (isAsync ? "async" : "sync"),
+ (eosMode ? "eos with last frame" : "eos separate"));
+ if (mSaveToMem) {
+ mOutputBuff = loopCounter == 0 ? ref : test;
+ mOutputBuff.reset();
+ }
+ mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ configureCodec(fmt, isAsync, eosMode, false);
+ MediaFormat defFormat = mCodec.getOutputFormat();
+ if (isFormatSimilar(defFormat, format)) {
+ if (ENABLE_LOGS) {
+ Log.d("Input format is same as default for format for %s", decoder);
+ }
+ validateFormat = false;
+ }
+ mCodec.start();
+ queueCodecConfig();
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(
+ log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ if (mSaveToMem && loopCounter != 0) {
+ assertTrue(log + "decoder output is flaky", ref.equals(test));
+ }
+ if (validateFormat) {
+ assertTrue(log + "not received format change",
+ mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+ mSignalledOutFormatChanged);
+ assertTrue(log + "configured format and output format are not similar",
+ isFormatSimilar(format,
+ mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+ mOutFormat));
+ }
+ loopCounter++;
+ }
+ }
+ }
+ mCodec.release();
+ }
+ mExtractor.release();
+ }
+
+ /**
+ * Test decoder for partial frame
+ */
+ @LargeTest
+ @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+ public void testDecodePartialFrame() throws IOException, InterruptedException {
+ MediaFormat format = setUpSource(mTestFile);
+ if (!mIsAudio) {
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+ }
+ ArrayList<MediaFormat> formats = new ArrayList<>();
+ formats.add(format);
+ ArrayList<String> listOfDecoders = selectCodecs(mMime, formats,
+ new String[]{MediaCodecInfo.CodecCapabilities.FEATURE_PartialFrame}, false);
+ boolean[] boolStates = {true, false};
+ int frameLimit = 10;
+ ByteBuffer buffer = ByteBuffer.allocate(4 * 1024 * 1024);
+ for (String decoder : listOfDecoders) {
+ decodeToMemory(mTestFile, decoder, 0, MediaExtractor.SEEK_TO_CLOSEST_SYNC, frameLimit);
+ mCodec = MediaCodec.createByCodecName(decoder);
+ OutputManager ref = mOutputBuff;
+ OutputManager test = new OutputManager();
+ mSaveToMem = true;
+ mOutputBuff = test;
+ for (boolean isAsync : boolStates) {
+ String log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+ mTestFile, (isAsync ? "async" : "sync"));
+ mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ test.reset();
+ configureCodec(format, isAsync, true, false);
+ mCodec.start();
+ doWork(frameLimit - 1);
+ ArrayList<MediaCodec.BufferInfo> list = createSubFrames(buffer, 4);
+ assertTrue("no sub frames in list received for " + mTestFile,
+ list != null && list.size() > 0);
+ doWork(buffer, list);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ assertTrue(log + "decoder output is not consistent with ref", ref.equals(test));
+ }
+ mCodec.release();
+ }
+ mExtractor.release();
+ }
+}
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
new file mode 100644
index 0000000..447e470
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2019 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.mediav2.cts;
+
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Validate encode functionality of available encoder components
+ */
+@RunWith(Parameterized.class)
+public class CodecEncoderTest extends CodecTestBase {
+ private static final String LOG_TAG = CodecEncoderTest.class.getSimpleName();
+ // files are in WorkDir.getMediaDirString();
+ private static final String mInputAudioFile = "bbb_2ch_44kHz_s16le.raw";
+ private static final String mInputVideoFile = "bbb_cif_yuv420p.yuv";
+
+ private final String mMime;
+ private final int[] mBitrates;
+ private final int[] mEncParamList1;
+ private final int[] mEncParamList2;
+ private final String mInputFile;
+ private ArrayList<MediaFormat> mFormats;
+ private byte[] mInputData;
+ private int mNumBytesSubmitted;
+ private long mInputOffsetPts;
+
+ private int mWidth, mHeight;
+ private int mChannels;
+ private int mRate;
+
+ public CodecEncoderTest(String mime, int[] bitrates, int[] encoderInfo1, int[] encoderInfo2) {
+ mMime = mime;
+ mBitrates = bitrates;
+ mEncParamList1 = encoderInfo1;
+ mEncParamList2 = encoderInfo2;
+ mAsyncHandle = new CodecAsyncHandler();
+ mFormats = new ArrayList<>();
+ mIsAudio = mMime.startsWith("audio/");
+ mInputFile = mIsAudio ? mInputAudioFile : mInputVideoFile;
+ }
+
+ @Override
+ void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
+ super.resetContext(isAsync, signalEOSWithLastFrame);
+ mNumBytesSubmitted = 0;
+ mInputOffsetPts = 0;
+ }
+
+ @Override
+ void flushCodec() {
+ super.flushCodec();
+ if (mIsAudio) {
+ mInputOffsetPts = (mNumBytesSubmitted + 1024) * 1000000L / (2 * mChannels * mRate);
+ } else {
+ mInputOffsetPts = (mInputCount + 5) * 1000000L / mRate;
+ }
+ mPrevOutputPts = mInputOffsetPts - 1;
+ mNumBytesSubmitted = 0;
+ }
+
+ private void setUpSource(String srcFile) throws IOException {
+ String inpPath = mInpPrefix + srcFile;
+ try (FileInputStream fInp = new FileInputStream(inpPath)) {
+ int size = (int) new File(inpPath).length();
+ mInputData = new byte[size];
+ fInp.read(mInputData, 0, size);
+ }
+ }
+
+ void fillImage(Image image) {
+ Assert.assertTrue(image.getFormat() == ImageFormat.YUV_420_888);
+ int imageWidth = image.getWidth();
+ int imageHeight = image.getHeight();
+ Image.Plane[] planes = image.getPlanes();
+ int offset = mNumBytesSubmitted;
+ 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;
+ }
+ if (pixelStride == 1) {
+ if (width == rowStride) {
+ buf.put(mInputData, offset, width * height);
+ } else {
+ for (y = 0; y < height; ++y) {
+ buf.put(mInputData, offset + y * width, width);
+ }
+ }
+ } else {
+ // do it pixel-by-pixel
+ for (y = 0; y < height; ++y) {
+ int lineOffset = y * rowStride;
+ for (x = 0; x < width; ++x) {
+ buf.position(lineOffset + x * pixelStride);
+ buf.put(mInputData[offset + y * width + x]);
+ }
+ }
+ }
+ offset += width * height;
+ }
+ }
+
+ void enqueueInput(int bufferIndex) {
+ ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
+ if (mNumBytesSubmitted >= mInputData.length) {
+ enqueueEOS(bufferIndex);
+ } else {
+ int size;
+ int flags = 0;
+ long pts = mInputOffsetPts;
+ if (mIsAudio) {
+ pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mRate);
+ size = Math.min(inputBuffer.capacity(), mInputData.length - mNumBytesSubmitted);
+ inputBuffer.put(mInputData, mNumBytesSubmitted, size);
+ } else {
+ pts += mInputCount * 1000000L / mRate;
+ size = mWidth * mHeight * 3 / 2;
+ if (mNumBytesSubmitted + size > mInputData.length) {
+ fail("received partial frame to encode");
+ } else {
+ Image img = mCodec.getInputImage(bufferIndex);
+ if (img != null) {
+ fillImage(img);
+ } else {
+ inputBuffer.put(mInputData, mNumBytesSubmitted, size);
+ }
+ }
+ }
+ if (mNumBytesSubmitted + size >= mInputData.length && mSignalEOSWithLastFrame) {
+ flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+ mSawInputEOS = true;
+ }
+ if (ENABLE_LOGS) {
+ Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts +
+ " flags: " + flags);
+ }
+ mCodec.queueInputBuffer(bufferIndex, 0, size, pts, flags);
+ mInputCount++;
+ mNumBytesSubmitted += size;
+ }
+ }
+
+ void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+ if (info.size > 0 && mSaveToMem &&
+ (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+ ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
+ mOutputBuff.saveToMemory(buf, info);
+ }
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ mSawOutputEOS = true;
+ }
+ if (ENABLE_LOGS) {
+ Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " +
+ info.size + " timestamp: " + info.presentationTimeUs);
+ }
+ if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+ /* TODO: skip pts check for video encoder with B frames */
+ if (info.presentationTimeUs <= mPrevOutputPts) {
+ fail("Timestamp ordering check failed: last timestamp: " + mPrevOutputPts +
+ " current timestamp:" + info.presentationTimeUs);
+ }
+ mPrevOutputPts = info.presentationTimeUs;
+ mOutputCount++;
+ }
+ mCodec.releaseOutputBuffer(bufferIndex, false);
+ }
+
+ private void encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format)
+ throws IOException, InterruptedException {
+ mSaveToMem = true;
+ mOutputBuff = new OutputManager();
+ mCodec = MediaCodec.createByCodecName(encoder);
+ setUpSource(file);
+ configureCodec(format, false, true, true);
+ if (mIsAudio) {
+ mRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
+ mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
+ } else {
+ mWidth = format.getInteger(MediaFormat.KEY_WIDTH, 352);
+ mHeight = format.getInteger(MediaFormat.KEY_HEIGHT, 288);
+ mRate = format.getInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ }
+ mCodec.start();
+ doWork(frameLimit);
+ queueEOS();
+ waitForAllOutputs();
+ mCodec.stop();
+ mCodec.release();
+ mSaveToMem = false;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> input() {
+ return Arrays.asList(new Object[][]{
+ // Audio - CodecMime, arrays of bit-rates, sample rates, channel counts
+ {MediaFormat.MIMETYPE_AUDIO_AAC, new int[]{64000, 128000}, new int[]{8000, 11025,
+ 22050, 44100, 48000}, new int[]{1, 2}},
+ {MediaFormat.MIMETYPE_AUDIO_OPUS, new int[]{6600, 8850, 12650, 14250, 15850,
+ 18250, 19850, 23050, 23850}, new int[]{16000}, new int[]{1}},
+ {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new int[]{4750, 5150, 5900, 6700, 7400, 7950,
+ 10200, 12200}, new int[]{8000}, new int[]{1}},
+ {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new int[]{6600, 8850, 12650, 14250, 15850,
+ 18250, 19850, 23050, 23850}, new int[]{16000}, new int[]{1}},
+ {MediaFormat.MIMETYPE_AUDIO_FLAC, new int[]{64000, 192000}, new int[]{8000, 48000
+ , 96000, 192000}, new int[]{1, 2}},
+
+ // Video - CodecMime, arrays of bit-rates, height, width
+ {MediaFormat.MIMETYPE_VIDEO_AVC, new int[]{512000}, new int[]{352}, new int[]{288}},
+ {MediaFormat.MIMETYPE_VIDEO_HEVC, new int[]{512000}, new int[]{352},
+ new int[]{288}},
+ {MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{512000}, new int[]{352}, new int[]{288}},
+ {MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{512000}, new int[]{352}, new int[]{288}},
+ });
+ }
+
+ public void setUpParams() {
+ for (int bitrate : mBitrates) {
+ if (mIsAudio) {
+ for (int rate : mEncParamList1) {
+ for (int channels : mEncParamList2) {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, mMime);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ format.setInteger(MediaFormat.KEY_SAMPLE_RATE, rate);
+ format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channels);
+ mFormats.add(format);
+ }
+ }
+ } else {
+ assertTrue("Wrong number of height, width parameters",
+ mEncParamList1.length == mEncParamList2.length);
+ for (int i = 0; i < mEncParamList1.length; i++) {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, mMime);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ format.setInteger(MediaFormat.KEY_WIDTH, mEncParamList1[i]);
+ format.setInteger(MediaFormat.KEY_HEIGHT, mEncParamList2[i]);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+ mFormats.add(format);
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests encoder for combinations:
+ * 1. Codec Sync Mode, Signal Eos with Last frame
+ * 2. Codec Sync Mode, Signal Eos Separately
+ * 3. Codec Async Mode, Signal Eos with Last frame
+ * 4. Codec Async Mode, Signal Eos Separately
+ * In all these scenarios, Timestamp ordering is verified. The output has to be
+ * consistent (not flaky) in all runs.
+ */
+ @LargeTest
+ @Ignore("TODO(b/149027258)")
+ @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+ public void testSimpleEncode() throws IOException, InterruptedException {
+ setUpParams();
+ ArrayList<String> listOfEncoders = selectCodecs(mMime, mFormats, null, true);
+ assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+ boolean[] boolStates = {true, false};
+ mSaveToMem = true;
+ setUpSource(mInputFile);
+ for (String encoder : listOfEncoders) {
+ mCodec = MediaCodec.createByCodecName(encoder);
+ for (MediaFormat format : mFormats) {
+ OutputManager ref = mSaveToMem ? new OutputManager() : null;
+ OutputManager test = mSaveToMem ? new OutputManager() : null;
+ int loopCounter = 0;
+ if (mIsAudio) {
+ mRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
+ mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
+ } else {
+ mWidth = format.getInteger(MediaFormat.KEY_WIDTH, 352);
+ mHeight = format.getInteger(MediaFormat.KEY_HEIGHT, 288);
+ mRate = format.getInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ }
+ for (boolean eosType : boolStates) {
+ for (boolean isAsync : boolStates) {
+ String log = String.format(
+ "format: %s \n codec: %s, file: %s, mode: %s, eos type: %s:: ",
+ format, encoder, mInputFile, (isAsync ? "async" : "sync"),
+ (eosType ? "eos with last frame" : "eos separate"));
+ if (mSaveToMem) {
+ mOutputBuff = loopCounter == 0 ? ref : test;
+ mOutputBuff.reset();
+ }
+ configureCodec(format, isAsync, eosType, true);
+ mCodec.start();
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(
+ log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ if (mSaveToMem && loopCounter != 0) {
+ assertTrue(log + "encoder output is flaky", ref.equals(test));
+ }
+ loopCounter++;
+ }
+ }
+ }
+ mCodec.release();
+ }
+ }
+
+ /**
+ * Tests flush when codec is in sync and async mode. In these scenarios, Timestamp
+ * ordering is verified. The output has to be consistent (not flaky) in all runs
+ */
+ @Ignore("TODO(b/147576107, b/148652492, b/148651699)")
+ @LargeTest
+ @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+ public void testFlush() throws IOException, InterruptedException {
+ ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+ assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+ setUpSource(mInputFile);
+ setUpParams();
+ boolean[] boolStates = {true, false};
+ for (String encoder : listOfEncoders) {
+ MediaFormat inpFormat = mFormats.get(0);
+ if (mIsAudio) {
+ mRate = inpFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+ mChannels = inpFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+ } else {
+ mWidth = inpFormat.getInteger(MediaFormat.KEY_WIDTH);
+ mHeight = inpFormat.getInteger(MediaFormat.KEY_HEIGHT);
+ mRate = inpFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
+ }
+ mCodec = MediaCodec.createByCodecName(encoder);
+ for (boolean isAsync : boolStates) {
+ String log = String.format("encoder: %s, input file: %s, mode: %s:: ", encoder,
+ mInputFile, (isAsync ? "async" : "sync"));
+ configureCodec(inpFormat, isAsync, true, true);
+ mCodec.start();
+
+ /* test flush in running state before queuing input */
+ flushCodec();
+ if (mIsCodecInAsyncMode) mCodec.start();
+ doWork(23);
+
+ /* test flush in running state */
+ flushCodec();
+ if (mIsCodecInAsyncMode) mCodec.start();
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+
+ /* test flush in eos state */
+ flushCodec();
+ if (mIsCodecInAsyncMode) mCodec.start();
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ }
+ mCodec.release();
+ }
+ }
+
+ /**
+ * Tests reconfigure when codec is in sync and async mode. In these
+ * scenarios, Timestamp ordering is verified. The output has to be consistent (not flaky)
+ * in all runs
+ */
+ @Ignore("TODO(b/148523403, b/149027258)")
+ @LargeTest
+ @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+ public void testReconfigure() throws IOException, InterruptedException {
+ ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+ assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+ setUpSource(mInputFile);
+ setUpParams();
+ boolean[] boolStates = {true, false};
+ for (String encoder : listOfEncoders) {
+ MediaFormat format = mFormats.get(0);
+ encodeToMemory(mInputFile, encoder, Integer.MAX_VALUE, format);
+ OutputManager ref = mOutputBuff;
+ OutputManager test = new OutputManager();
+ mOutputBuff = test;
+ mCodec = MediaCodec.createByCodecName(encoder);
+ for (boolean isAsync : boolStates) {
+ String log = String.format("encoder: %s, input file: %s, mode: %s:: ", encoder,
+ mInputFile, (isAsync ? "async" : "sync"));
+ configureCodec(format, isAsync, true, true);
+
+ /* test reconfigure in stopped state */
+ reConfigureCodec(format, !isAsync, false, true);
+ mCodec.start();
+
+ /* test reconfigure in running state before queuing input */
+ reConfigureCodec(format, !isAsync, false, true);
+ mCodec.start();
+ doWork(23);
+
+ /* test reconfigure codec in running state */
+ reConfigureCodec(format, isAsync, true, true);
+ mCodec.start();
+ mSaveToMem = true;
+ test.reset();
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ assertTrue(log + "encoder output is flaky", ref.equals(test));
+
+ /* test reconfigure codec at eos state */
+ reConfigureCodec(format, !isAsync, false, true);
+ mCodec.start();
+ test.reset();
+ doWork(Integer.MAX_VALUE);
+ queueEOS();
+ waitForAllOutputs();
+ mCodec.stop();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ assertTrue(log + "no input sent", 0 != mInputCount);
+ assertTrue(log + "output received", 0 != mOutputCount);
+ if (!mIsAudio) {
+ assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+ " / " + mInputCount, mInputCount == mOutputCount);
+ }
+ assertTrue(log + "encoder output is flaky", ref.equals(test));
+ mSaveToMem = false;
+ }
+ mCodec.release();
+ }
+ }
+
+ /**
+ * Tests encoder for only EOS frame
+ */
+ @SmallTest
+ @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
+ public void testOnlyEos() throws IOException, InterruptedException {
+ ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+ assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+ setUpParams();
+ boolean[] boolStates = {true, false};
+ for (String encoder : listOfEncoders) {
+ mCodec = MediaCodec.createByCodecName(encoder);
+ for (boolean isAsync : boolStates) {
+ String log = String.format("encoder: %s, input file: %s, mode: %s:: ", encoder,
+ mInputFile, (isAsync ? "async" : "sync"));
+ configureCodec(mFormats.get(0), isAsync, false, true);
+ mCodec.start();
+ queueEOS();
+ waitForAllOutputs();
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+ }
+ mCodec.release();
+ }
+ }
+}
diff --git a/tests/media/src/android/mediav2/cts/CodecTestBase.java b/tests/media/src/android/mediav2/cts/CodecTestBase.java
new file mode 100644
index 0000000..a09cf5a
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecTestBase.java
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2019 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.mediav2.cts;
+
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.zip.CRC32;
+
+import static org.junit.Assert.assertTrue;
+
+class CodecAsyncHandler extends MediaCodec.Callback {
+ private static final String LOG_TAG = CodecAsyncHandler.class.getSimpleName();
+ private final Lock mLock = new ReentrantLock();
+ private final Condition mCondition = mLock.newCondition();
+ private CallBackQueue mCbInputQueue;
+ private CallBackQueue mCbOutputQueue;
+ private MediaFormat mOutFormat;
+ private boolean mSignalledOutFormatChanged;
+ private volatile boolean mSignalledError;
+
+ CodecAsyncHandler() {
+ mCbInputQueue = new CallBackQueue();
+ mCbOutputQueue = new CallBackQueue();
+ mSignalledError = false;
+ mSignalledOutFormatChanged = false;
+ }
+
+ void clearQueues() {
+ mCbInputQueue.clear();
+ mCbOutputQueue.clear();
+ }
+
+ void resetContext() {
+ clearQueues();
+ mOutFormat = null;
+ mSignalledOutFormatChanged = false;
+ mSignalledError = false;
+ }
+
+ @Override
+ public void onInputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex) {
+ assertTrue(bufferIndex >= 0);
+ mCbInputQueue.push(new Pair<>(bufferIndex, (MediaCodec.BufferInfo) null));
+ }
+
+ @Override
+ public void onOutputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex,
+ @NonNull MediaCodec.BufferInfo info) {
+ assertTrue(bufferIndex >= 0);
+ mCbOutputQueue.push(new Pair<>(bufferIndex, info));
+ }
+
+ @Override
+ public void onError(@NonNull MediaCodec codec, MediaCodec.CodecException e) {
+ mLock.lock();
+ mSignalledError = true;
+ mCondition.signalAll();
+ mLock.unlock();
+ Log.e(LOG_TAG, "received media codec error : " + e.getMessage());
+ }
+
+ @Override
+ public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
+ mOutFormat = format;
+ mSignalledOutFormatChanged = true;
+ Log.i(LOG_TAG, "Output format changed: " + format.toString());
+ }
+
+ void setCallBack(MediaCodec codec, boolean isCodecInAsyncMode) {
+ if (isCodecInAsyncMode) {
+ codec.setCallback(this);
+ } else {
+ codec.setCallback(null);
+ }
+ }
+
+ Pair<Integer, MediaCodec.BufferInfo> getInput() throws InterruptedException {
+ Pair<Integer, MediaCodec.BufferInfo> element = null;
+ while (!mSignalledError) {
+ if (mCbInputQueue.isEmpty()) {
+ mLock.lock();
+ mCondition.await();
+ mLock.unlock();
+ } else {
+ element = mCbInputQueue.pop();
+ break;
+ }
+ }
+ return element;
+ }
+
+ Pair<Integer, MediaCodec.BufferInfo> getOutput() throws InterruptedException {
+ Pair<Integer, MediaCodec.BufferInfo> element = null;
+ while (!mSignalledError) {
+ if (mCbOutputQueue.isEmpty()) {
+ mLock.lock();
+ mCondition.await();
+ mLock.unlock();
+ } else {
+ element = mCbOutputQueue.pop();
+ break;
+ }
+ }
+ return element;
+ }
+
+ Pair<Integer, MediaCodec.BufferInfo> getWork() throws InterruptedException {
+ Pair<Integer, MediaCodec.BufferInfo> element = null;
+ while (!mSignalledError) {
+ if (mCbInputQueue.isEmpty() && mCbOutputQueue.isEmpty()) {
+ mLock.lock();
+ mCondition.await();
+ mLock.unlock();
+ }
+ if (!mCbOutputQueue.isEmpty()) {
+ element = mCbOutputQueue.pop();
+ break;
+ }
+ if (!mCbInputQueue.isEmpty()) {
+ element = mCbInputQueue.pop();
+ break;
+ }
+ }
+ return element;
+ }
+
+ boolean hasSeenError() {
+ return mSignalledError;
+ }
+
+ boolean hasOutputFormatChanged() {
+ return mSignalledOutFormatChanged;
+ }
+
+ MediaFormat getOutputFormat() {
+ return mOutFormat;
+ }
+
+ private class CallBackQueue {
+ private final LinkedList<Pair<Integer, MediaCodec.BufferInfo>> mQueue = new LinkedList<>();
+
+ void push(Pair<Integer, MediaCodec.BufferInfo> element) {
+ mLock.lock();
+ mQueue.add(element);
+ mCondition.signalAll();
+ mLock.unlock();
+ }
+
+ Pair<Integer, MediaCodec.BufferInfo> pop() {
+ Pair<Integer, MediaCodec.BufferInfo> element = null;
+ mLock.lock();
+ if (mQueue.size() != 0) element = mQueue.remove(0);
+ mLock.unlock();
+ return element;
+ }
+
+ boolean isEmpty() {
+ mLock.lock();
+ boolean isEmpty = (mQueue.size() == 0);
+ mLock.unlock();
+ return isEmpty;
+ }
+
+ void clear() {
+ mLock.lock();
+ mQueue.clear();
+ mLock.unlock();
+ }
+ }
+}
+
+class OutputManager {
+ private static final String LOG_TAG = OutputManager.class.getSimpleName();
+ private byte[] memory;
+ private int memIndex;
+ private ArrayList<Long> crc32List;
+
+ OutputManager() {
+ memory = new byte[1024];
+ memIndex = 0;
+ crc32List = new ArrayList<>();
+ }
+
+ void checksum(ByteBuffer buf, int size) {
+ int cap = buf.capacity();
+ assertTrue("checksum() params are invalid: size = " + size + " cap = " + cap,
+ size > 0 && size <= cap);
+ CRC32 crc = new CRC32();
+ if (buf.hasArray()) {
+ crc.update(buf.array(), buf.position() + buf.arrayOffset(), size);
+ } else {
+ int pos = buf.position();
+ final int rdsize = Math.min(4096, size);
+ byte[] bb = new byte[rdsize];
+ int chk;
+ for (int i = 0; i < size; i += chk) {
+ chk = Math.min(rdsize, size - i);
+ buf.get(bb, 0, chk);
+ crc.update(bb, 0, chk);
+ }
+ buf.position(pos);
+ }
+ crc32List.add(crc.getValue());
+ }
+
+ void checksum(Image image) {
+ int format = image.getFormat();
+ if (format != ImageFormat.YUV_420_888) {
+ crc32List.add(-1L);
+ return;
+ }
+ CRC32 crc = new CRC32();
+ 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);
+ }
+ crc.update(bb, 0, width * height);
+ }
+ crc32List.add(crc.getValue());
+ }
+
+ void saveToMemory(ByteBuffer buf, MediaCodec.BufferInfo info) {
+ if (memIndex + info.size >= memory.length) {
+ memory = Arrays.copyOf(memory, memIndex + info.size);
+ }
+ buf.position(info.offset);
+ buf.get(memory, memIndex, info.size);
+ memIndex += info.size;
+ }
+
+ void position(int index) {
+ if (index < 0 || index >= memory.length) index = 0;
+ memIndex = index;
+ }
+
+ void reset() {
+ position(0);
+ crc32List.clear();
+ }
+
+ float getRmsError(short[] refData) {
+ long totalErrorSquared = 0;
+ assertTrue(0 == (memory.length & 1));
+ short[] shortData = new short[memory.length / 2];
+ ByteBuffer.wrap(memory).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shortData);
+ if (refData.length != shortData.length) return Float.MAX_VALUE;
+ for (int i = 0; i < shortData.length; i++) {
+ int d = shortData[i] - refData[i];
+ totalErrorSquared += d * d;
+ }
+ long avgErrorSquared = (totalErrorSquared / shortData.length);
+ return (float) Math.sqrt(avgErrorSquared);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ OutputManager that = (OutputManager) o;
+ boolean isEqual = true;
+ if (!crc32List.equals(that.crc32List)) {
+ isEqual = false;
+ Log.e(LOG_TAG, "ref and test crc32 checksums mismatch");
+ }
+ if (memIndex == that.memIndex) {
+ int count = 0;
+ for (int i = 0; i < memIndex; i++) {
+ if (memory[i] != that.memory[i]) {
+ count++;
+ if (count < 20) {
+ Log.d(LOG_TAG, "sample at offset " + i + " exp/got:: " + memory[i] + '/' +
+ that.memory[i]);
+ }
+ }
+ }
+ if (count != 0) {
+ isEqual = false;
+ Log.e(LOG_TAG, "ref and test o/p samples mismatch " + count);
+ }
+ } else {
+ isEqual = false;
+ Log.e(LOG_TAG, "ref and test o/p sizes mismatch " + memIndex + '/' + that.memIndex);
+ }
+ return isEqual;
+ }
+}
+
+abstract class CodecTestBase {
+ private static final String LOG_TAG = CodecTestBase.class.getSimpleName();
+ static final boolean ENABLE_LOGS = false;
+ static final int PER_TEST_TIMEOUT_LARGE_TEST_MS = 300000;
+ static final int PER_TEST_TIMEOUT_SMALL_TEST_MS = 60000;
+ static final long Q_DEQ_TIMEOUT_US = 500;
+ static final String mInpPrefix = WorkDir.getMediaDirString();
+
+ CodecAsyncHandler mAsyncHandle;
+ boolean mIsCodecInAsyncMode;
+ boolean mSawInputEOS;
+ boolean mSawOutputEOS;
+ boolean mSignalEOSWithLastFrame;
+ int mInputCount;
+ int mOutputCount;
+ long mPrevOutputPts;
+ boolean mSignalledOutFormatChanged;
+ MediaFormat mOutFormat;
+ boolean mIsAudio;
+
+ boolean mSaveToMem;
+ OutputManager mOutputBuff;
+
+ MediaCodec mCodec;
+
+ abstract void enqueueInput(int bufferIndex) throws IOException;
+
+ abstract void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info);
+
+ void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
+ boolean isEncoder) {
+ resetContext(isAsync, signalEOSWithLastFrame);
+ mAsyncHandle.setCallBack(mCodec, isAsync);
+ mCodec.configure(format, null, null, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+ if (ENABLE_LOGS) {
+ Log.v(LOG_TAG, "codec configured");
+ }
+ }
+
+ void flushCodec() {
+ mCodec.flush();
+ mAsyncHandle.clearQueues();
+ mSawInputEOS = false;
+ mSawOutputEOS = false;
+ mInputCount = 0;
+ mOutputCount = 0;
+ mPrevOutputPts = Long.MIN_VALUE;
+ if (ENABLE_LOGS) {
+ Log.v(LOG_TAG, "codec flushed");
+ }
+ }
+
+ void reConfigureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
+ boolean isEncoder) {
+ /* TODO(b/147348711) */
+ if (false) mCodec.stop();
+ else mCodec.reset();
+ configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder);
+ }
+
+ void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
+ mAsyncHandle.resetContext();
+ mIsCodecInAsyncMode = isAsync;
+ mSawInputEOS = false;
+ mSawOutputEOS = false;
+ mSignalEOSWithLastFrame = signalEOSWithLastFrame;
+ mInputCount = 0;
+ mOutputCount = 0;
+ mPrevOutputPts = Long.MIN_VALUE;
+ mSignalledOutFormatChanged = false;
+ }
+
+ void enqueueEOS(int bufferIndex) {
+ if (!mSawInputEOS) {
+ mCodec.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ mSawInputEOS = true;
+ if (ENABLE_LOGS) {
+ Log.v(LOG_TAG, "Queued End of Stream");
+ }
+ }
+ }
+
+ void doWork(int frameLimit) throws InterruptedException, IOException {
+ int frameCount = 0;
+ if (mIsCodecInAsyncMode) {
+ // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
+ while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < frameLimit) {
+ Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
+ if (element != null) {
+ int bufferID = element.first;
+ MediaCodec.BufferInfo info = element.second;
+ if (info != null) {
+ dequeueOutput(bufferID, info);
+ } else {
+ enqueueInput(bufferID);
+ frameCount++;
+ }
+ }
+ }
+ } else {
+ MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+ // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
+ while (!mSawInputEOS && frameCount < frameLimit) {
+ int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+ if (outputBufferId >= 0) {
+ dequeueOutput(outputBufferId, outInfo);
+ } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ mOutFormat = mCodec.getOutputFormat();
+ mSignalledOutFormatChanged = true;
+ }
+ int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
+ if (inputBufferId != -1) {
+ enqueueInput(inputBufferId);
+ frameCount++;
+ }
+ }
+ }
+ }
+
+ void queueEOS() throws InterruptedException {
+ if (!mSawInputEOS) {
+ if (mIsCodecInAsyncMode) {
+ Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getInput();
+ if (element != null) {
+ enqueueEOS(element.first);
+ }
+ } else {
+ enqueueEOS(mCodec.dequeueInputBuffer(-1));
+ }
+ }
+ }
+
+ void waitForAllOutputs() throws InterruptedException {
+ if (mIsCodecInAsyncMode) {
+ while (!mAsyncHandle.hasSeenError() && !mSawOutputEOS) {
+ Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput();
+ if (element != null) {
+ dequeueOutput(element.first, element.second);
+ }
+ }
+ } else {
+ MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+ while (!mSawOutputEOS) {
+ int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+ if (outputBufferId >= 0) {
+ dequeueOutput(outputBufferId, outInfo);
+ } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ mOutFormat = mCodec.getOutputFormat();
+ mSignalledOutFormatChanged = true;
+ }
+ }
+ }
+ }
+
+ static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats,
+ String[] features, boolean isEncoder) {
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+ ArrayList<String> listOfDecoders = new ArrayList<>();
+ for (MediaCodecInfo codecInfo : codecInfos) {
+ if (codecInfo.isEncoder() != isEncoder) continue;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
+ String[] types = codecInfo.getSupportedTypes();
+ for (String type : types) {
+ if (type.equalsIgnoreCase(mime)) {
+ boolean isOk = true;
+ MediaCodecInfo.CodecCapabilities codecCapabilities =
+ codecInfo.getCapabilitiesForType(type);
+ if (formats != null) {
+ for (MediaFormat format : formats) {
+ if (!codecCapabilities.isFormatSupported(format)) {
+ isOk = false;
+ break;
+ }
+ }
+ }
+ if (features != null) {
+ for (String feature : features) {
+ if (!codecCapabilities.isFeatureSupported(feature)) {
+ isOk = false;
+ break;
+ }
+ }
+ }
+ if (isOk) listOfDecoders.add(codecInfo.getName());
+ }
+ }
+ }
+ return listOfDecoders;
+ }
+
+ static int getWidth(MediaFormat format) {
+ int width = format.getInteger(MediaFormat.KEY_WIDTH);
+ if (format.containsKey("crop-left") && format.containsKey("crop-right")) {
+ width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left");
+ }
+ return width;
+ }
+
+ static int getHeight(MediaFormat format) {
+ int height = format.getInteger(MediaFormat.KEY_HEIGHT);
+ if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) {
+ height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top");
+ }
+ return height;
+ }
+
+ static boolean isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat) {
+ if (inpFormat == null || outFormat == null) return false;
+ String inpMime = inpFormat.getString(MediaFormat.KEY_MIME);
+ String outMime = inpFormat.getString(MediaFormat.KEY_MIME);
+ if (outMime.startsWith("audio/")) {
+ return inpFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) ==
+ outFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) &&
+ inpFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) ==
+ outFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) &&
+ inpMime.startsWith("audio/");
+ } else if (outMime.startsWith("video/")) {
+ return getWidth(inpFormat) == getWidth(outFormat) &&
+ getHeight(inpFormat) == getHeight(outFormat) && inpMime.startsWith("video/");
+ }
+ return true;
+ }
+}
diff --git a/tests/media/src/android/mediav2/cts/ExtractorTest.java b/tests/media/src/android/mediav2/cts/ExtractorTest.java
index b4c4d01..8a71d14 100644
--- a/tests/media/src/android/mediav2/cts/ExtractorTest.java
+++ b/tests/media/src/android/mediav2/cts/ExtractorTest.java
@@ -254,15 +254,25 @@
'/' + testSampleInfo.presentationTimeUs);
}
areTracksIdentical = false;
+ break;
}
int refSz = refExtractor.readSampleData(refBuffer, 0);
- int testSz = testExtractor.readSampleData(testBuffer, 0);
- if (refSz != testSz || refSz != refSampleInfo.size) {
+ if (refSz != refSampleInfo.size) {
if (ENABLE_LOGS) {
- Log.d(LOG_TAG, "Mime: " + refMime + " Size exp/got/got: " +
- refSz + '/' + testSz + '/' + refSampleInfo.size);
+ Log.d(LOG_TAG, "Mime: " + refMime + " Size exp/got: " +
+ refSampleInfo.size + '/' + refSz);
}
areTracksIdentical = false;
+ break;
+ }
+ int testSz = testExtractor.readSampleData(testBuffer, 0);
+ if (testSz != testSampleInfo.size) {
+ if (ENABLE_LOGS) {
+ Log.d(LOG_TAG, "Mime: " + refMime + " Size exp/got: " +
+ testSampleInfo.size + '/' + testSz);
+ }
+ areTracksIdentical = false;
+ break;
}
int trackIndex = refExtractor.getSampleTrackIndex();
if (trackIndex != refTrackID) {
@@ -271,6 +281,7 @@
" TrackID exp/got: " + refTrackID + '/' + trackIndex);
}
areTracksIdentical = false;
+ break;
}
trackIndex = testExtractor.getSampleTrackIndex();
if (trackIndex != testTrackID) {
@@ -279,6 +290,7 @@
" TrackID exp/got: " + testTrackID + '/' + trackIndex);
}
areTracksIdentical = false;
+ break;
}
boolean haveRefSamples = refExtractor.advance();
boolean haveTestSamples = testExtractor.advance();
@@ -287,6 +299,7 @@
Log.d(LOG_TAG, "Mime: " + refMime + " Mismatch in sampleCount");
}
areTracksIdentical = false;
+ break;
}
if (!haveRefSamples && !isExtractorOKonEOS(refExtractor)) {
@@ -294,12 +307,14 @@
Log.d(LOG_TAG, "Mime: " + refMime + " calls post advance() are not OK");
}
areTracksIdentical = false;
+ break;
}
if (!haveTestSamples && !isExtractorOKonEOS(testExtractor)) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "Mime: " + refMime + " calls post advance() are not OK");
}
areTracksIdentical = false;
+ break;
}
if (ENABLE_LOGS) {
Log.v(LOG_TAG, "Mime: " + refMime + " Sample: " + frameCount +
@@ -307,8 +322,7 @@
" size: " + refSampleInfo.size +
" ts: " + refSampleInfo.presentationTimeUs);
}
- if (!areTracksIdentical || !haveRefSamples || !haveTestSamples ||
- frameCount >= sampleLimit) {
+ if (!haveRefSamples || frameCount >= sampleLimit) {
break;
}
}
@@ -343,6 +357,10 @@
private MediaExtractor mRefExtractor;
+ static {
+ System.loadLibrary("ctsmediav2extractor_jni");
+ }
+
@Before
public void setUp() throws IOException {
mRefExtractor = new MediaExtractor();
@@ -538,6 +556,14 @@
testExtractor.unselectTrack(0);
testExtractor.release();
}
+
+ private native boolean nativeTestDataSource(String srcPath, String srcUrl);
+
+ @Test
+ public void testDataSourceNative() {
+ assertTrue(testName.getMethodName() + " failed ",
+ nativeTestDataSource(mInpPrefix + mInpMedia, mInpMediaUrl));
+ }
}
/**
@@ -551,6 +577,10 @@
private String[] mSrcFiles;
private String mMime;
+ static {
+ System.loadLibrary("ctsmediav2extractor_jni");
+ }
+
@Rule
public TestName testName = new TestName();
@@ -618,6 +648,16 @@
});
}
+ private native boolean nativeTestExtract(String srcPath, String refPath, String mime);
+
+ private native boolean nativeTestSeek(String srcPath, String mime);
+
+ private native boolean nativeTestSeekFlakiness(String srcPath, String mime);
+
+ private native boolean nativeTestSeekToZero(String srcPath, String mime);
+
+ private native boolean nativeTestFileFormat(String srcPath);
+
public FunctionalityTest(String mime, String[] srcFiles) {
mMime = mime;
mSrcFiles = srcFiles;
@@ -763,12 +803,13 @@
if (!isSampleInfoIdentical(arg.mExpected, received)) {
errCnt++;
if (ENABLE_LOGS) {
- Log.d(LOG_TAG, " flags exp/got: " +
- received.flags + '/' + arg.mExpected.flags);
- Log.d(LOG_TAG, " size exp/got: " +
- received.size + '/' + arg.mExpected.size);
- Log.d(LOG_TAG, " ts exp/got: " + received.presentationTimeUs +
- '/' + arg.mExpected.presentationTimeUs);
+ Log.d(LOG_TAG, " flags exp/got: " + arg.mExpected.flags + '/' +
+ received.flags);
+ Log.d(LOG_TAG,
+ " size exp/got: " + arg.mExpected.size + '/' + received.size);
+ Log.d(LOG_TAG,
+ " ts exp/got: " + arg.mExpected.presentationTimeUs + '/' +
+ received.presentationTimeUs);
}
}
}
@@ -798,7 +839,7 @@
MediaExtractor refExtractor = new MediaExtractor();
refExtractor.setDataSource(mInpPrefix + mSrcFiles[0]);
boolean isOk = true;
- for (int i = 1; i < mSrcFiles.length; i++) {
+ for (int i = 1; i < mSrcFiles.length && isOk; i++) {
MediaExtractor testExtractor = new MediaExtractor();
testExtractor.setDataSource(mInpPrefix + mSrcFiles[i]);
if (!isMediaSimilar(refExtractor, testExtractor, mMime, Integer.MAX_VALUE)) {
@@ -840,6 +881,7 @@
}
if (!codecListSupp.contains(mMime)) {
isOk = false;
+ break;
}
}
}
@@ -867,6 +909,7 @@
}
if (!codecListSupp.contains(mMime)) {
isOk = false;
+ break;
}
}
}
@@ -900,12 +943,42 @@
extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
currInfo.set(0, (int) extractor.getSampleSize(),
extractor.getSampleTime(), extractor.getSampleFlags());
- extractor.unselectTrack(trackID);
if (!isSampleInfoIdentical(sampleInfoAtZero, currInfo)) {
if (!codecListSupp.contains(mMime)) {
+ if (ENABLE_LOGS) {
+ Log.d(LOG_TAG, "seen mismatch seekTo(0, SEEK_TO_CLOSEST_SYNC)");
+ Log.d(LOG_TAG, " flags exp/got: " + sampleInfoAtZero.flags + '/' +
+ currInfo.flags);
+ Log.d(LOG_TAG, " size exp/got: " + sampleInfoAtZero.size + '/' +
+ currInfo.size);
+ Log.d(LOG_TAG,
+ " ts exp/got: " + sampleInfoAtZero.presentationTimeUs +
+ '/' + currInfo.presentationTimeUs);
+ }
isOk = false;
+ break;
}
}
+ extractor.seekTo(-1L, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ currInfo.set(0, (int) extractor.getSampleSize(),
+ extractor.getSampleTime(), extractor.getSampleFlags());
+ if (!isSampleInfoIdentical(sampleInfoAtZero, currInfo)) {
+ if (!codecListSupp.contains(mMime)) {
+ if (ENABLE_LOGS) {
+ Log.d(LOG_TAG, "seen mismatch seekTo(-1, SEEK_TO_CLOSEST_SYNC)");
+ Log.d(LOG_TAG, " flags exp/got: " + sampleInfoAtZero.flags + '/' +
+ currInfo.flags);
+ Log.d(LOG_TAG, " size exp/got: " + sampleInfoAtZero.size + '/' +
+ currInfo.size);
+ Log.d(LOG_TAG,
+ " ts exp/got: " + sampleInfoAtZero.presentationTimeUs +
+ '/' + currInfo.presentationTimeUs);
+ }
+ isOk = false;
+ break;
+ }
+ }
+ extractor.unselectTrack(trackID);
}
extractor.release();
}
@@ -928,5 +1001,94 @@
extractor.release();
}
}
+
+ @LargeTest
+ @Test
+ public void testExtractNative() {
+ assumeTrue(shouldRunTest(mMime));
+ assertTrue(mSrcFiles.length > 1);
+ assumeTrue("TODO(b/146925481)", mMime == MediaFormat.MIMETYPE_VIDEO_VP8 ||
+ mMime == MediaFormat.MIMETYPE_VIDEO_VP9 ||
+ mMime == MediaFormat.MIMETYPE_VIDEO_AV1 ||
+ mMime == MediaFormat.MIMETYPE_AUDIO_FLAC);
+ boolean isOk = true;
+ for (int i = 1; i < mSrcFiles.length; i++) {
+ if (!nativeTestExtract(mInpPrefix + mSrcFiles[0], mInpPrefix + mSrcFiles[i],
+ mMime)) {
+ Log.d(LOG_TAG, "Files: " + mSrcFiles[0] + ", " + mSrcFiles[i] +
+ " are different from extractor perpsective");
+ if (!codecListSupp.contains(mMime)) {
+ isOk = false;
+ break;
+ }
+ }
+ }
+ assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+ }
+
+ @LargeTest
+ @Test
+ @Ignore("TODO(b/146420831)")
+ public void testSeekNative() {
+ assumeTrue(shouldRunTest(mMime));
+ boolean isOk = true;
+ for (String srcFile : mSrcFiles) {
+ if (!nativeTestSeek(mInpPrefix + srcFile, mMime)) {
+ if (!codecListSupp.contains(mMime)) {
+ isOk = false;
+ break;
+ }
+ }
+ }
+ assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+ }
+
+ @LargeTest
+ @Test
+ public void testSeekFlakinessNative() {
+ assumeTrue(shouldRunTest(mMime));
+ boolean isOk = true;
+ for (String srcFile : mSrcFiles) {
+ if (!nativeTestSeekFlakiness(mInpPrefix + srcFile, mMime)) {
+ if (!codecListSupp.contains(mMime)) {
+ isOk = false;
+ break;
+ }
+ }
+ }
+ assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+ }
+
+ @SmallTest
+ @Test
+ public void testSeekToZeroNative() {
+ assumeTrue(shouldRunTest(mMime));
+ assumeTrue("TODO(b/146925481)", mMime != MediaFormat.MIMETYPE_AUDIO_MPEG &&
+ mMime != MediaFormat.MIMETYPE_AUDIO_AAC);
+ boolean isOk = true;
+ for (String srcFile : mSrcFiles) {
+ if (!nativeTestSeekToZero(mInpPrefix + srcFile, mMime)) {
+ if (!codecListSupp.contains(mMime)) {
+ isOk = false;
+ break;
+ }
+ }
+ }
+ assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+ }
+
+ @SmallTest
+ @Test
+ public void testFileFormatNative() {
+ assumeTrue(shouldRunTest(mMime));
+ boolean isOk = true;
+ for (String srcFile : mSrcFiles) {
+ if (!nativeTestFileFormat(mInpPrefix + srcFile)) {
+ isOk = false;
+ break;
+ }
+ assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+ }
+ }
}
}
diff --git a/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java b/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java
new file mode 100644
index 0000000..84122cd
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2019 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.mediav2.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaDataSource;
+import android.media.MediaExtractor;
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(Enclosed.class)
+public class ExtractorUnitTest {
+ private static final int MAX_SAMPLE_SIZE = 4 * 1024 * 1024;
+ private static final String mInpPrefix = WorkDir.getMediaDirString();
+ private static final String mInpMedia = "ForBiggerEscapes.mp4";
+
+ @SmallTest
+ public static class TestApi {
+ @Rule
+ public TestName testName = new TestName();
+
+ @Test
+ public void testGetTrackCountBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ assertTrue("received valid trackCount before setDataSource",
+ extractor.getTrackCount() <= 0);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetTrackCountAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.getTrackCount();
+ fail("getTrackCount succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testSelectTrackBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.selectTrack(0);
+ fail("selectTrack succeeds before setDataSource");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testSelectTrackForInvalidIndex() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ try {
+ extractor.selectTrack(-1);
+ fail("selectTrack succeeds for track index: -1");
+ } catch (Exception e) {
+ // expected
+ }
+ try {
+ extractor.selectTrack(extractor.getTrackCount());
+ fail("selectTrack succeeds for out of bounds track index: " +
+ extractor.getTrackCount());
+ } catch (Exception e) {
+ // expected
+ }
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testIdempotentSelectTrack() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ extractor.selectTrack(0);
+ extractor.selectTrack(0);
+ } catch (Exception e) {
+ fail("multiple selection of same track has failed");
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testSelectTrackAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.selectTrack(0);
+ fail("selectTrack succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testUnselectTrackBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.unselectTrack(0);
+ fail("unselectTrack succeeds before setDataSource");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testUnselectTrackForInvalidIndex() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ try {
+ extractor.unselectTrack(-1);
+ fail("unselectTrack succeeds for track index: -1");
+ } catch (Exception e) {
+ // expected
+ }
+ try {
+ extractor.unselectTrack(extractor.getTrackCount());
+ fail("unselectTrack succeeds for out of bounds track index: " +
+ extractor.getTrackCount());
+ } catch (Exception e) {
+ // expected
+ }
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testUnselectTrackForUnSelectedTrackIndex() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ extractor.unselectTrack(0);
+ } catch (Exception e) {
+ fail("un-selection of non-selected track has failed");
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testIdempotentUnselectTrack() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ extractor.selectTrack(0);
+ extractor.unselectTrack(0);
+ extractor.unselectTrack(0);
+ } catch (Exception e) {
+ fail("multiple un-selection of selected track has failed");
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testUnselectTrackAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.unselectTrack(0);
+ fail("unselectTrack succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testSeekToBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.seekTo(33000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ if (extractor.getSampleTime() != -1 || extractor.getSampleSize() != -1 ||
+ extractor.getSampleFlags() != -1) {
+ fail("seekTo() succeeds before setting data source, returns non-negative " +
+ "sampleTime / sampleSize / sampleFlags");
+ }
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testSeekToBeforeSelectTrack() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ extractor.seekTo(33000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ if (extractor.getSampleTime() != -1 || extractor.getSampleSize() != -1 ||
+ extractor.getSampleFlags() != -1) {
+ fail("seekTo() succeeds before selectTrack, returns non-negative " +
+ "sampleTime / sampleSize / sampleFlags");
+ }
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testSeekToForInvalidMode() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ extractor.selectTrack(0);
+ long pts = 33000;
+ try {
+ extractor.seekTo(pts, (int) pts);
+ fail("seekTo() succeeds for invalid mode: " + pts);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testSeekToAfterRelease() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ extractor.release();
+ try {
+ extractor.seekTo(33000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ fail("seekTo() succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/148205432)")
+ public void testGetCachedDurationBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ assertTrue("received valid cachedDuration before setDataSource",
+ extractor.getCachedDuration() == -1);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetCachedDurationAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.getCachedDuration();
+ fail("cachedDuration succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/148204634)")
+ public void testHasCacheReachedEOSBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ assertTrue("unexpected value received from hasCacheReachedEndOfStream before" +
+ " setDataSource", !extractor.hasCacheReachedEndOfStream());
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testHasCacheReachedEOSAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.hasCacheReachedEndOfStream();
+ fail("hasCacheReachedEndOfStream succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetMetricsBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ assertTrue("received valid metrics before setDataSource",
+ extractor.getMetrics() == null);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetMetricsAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.getMetrics();
+ fail("getMetrics() succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testIdempotentRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.release();
+ extractor.release();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testAdvanceBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ assertTrue("advance succeeds before setDataSource", !extractor.advance());
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testAdvanceBeforeSelectTrack() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ assertTrue("advance succeeds without any active tracks", !extractor.advance());
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testAdvanceAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.advance();
+ fail("advance succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetSampleFlagsBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ assertTrue("received valid sampleFlag before setDataSource",
+ extractor.getSampleFlags() == -1);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetSampleFlagsBeforeSelectTrack() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ assertTrue("received valid sampleFlag without any active tracks",
+ extractor.getSampleFlags() == -1);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetSampleFlagsAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.getSampleFlags();
+ fail("getSampleFlags succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetSampleTimeBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ assertTrue("received valid sampleTime before setDataSource",
+ extractor.getSampleTime() == -1);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetSampleTimeBeforeSelectTrack() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ assertTrue("received valid sampleTime without any active tracks",
+ extractor.getSampleTime() == -1);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetSampleTimeAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.getSampleTime();
+ fail("getSampleTime succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetSampleSizeBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ assertTrue("received valid sampleSize before setDataSource",
+ extractor.getSampleSize() == -1);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetSampleSizeBeforeSelectTrack() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ assertTrue("received valid sampleSize without any active tracks",
+ extractor.getSampleSize() == -1);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetSampleSizeAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.getSampleSize();
+ fail("getSampleSize succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetSampleTrackIndexBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ assertTrue("received valid sampleTrackIndex before setDataSource",
+ extractor.getSampleTrackIndex() == -1);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetSampleTrackIndexBeforeSelectTrack() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ assertTrue("received valid sampleTrackIndex without any active tracks",
+ extractor.getSampleTrackIndex() == -1);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetSampleTrackIndexAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.getSampleTrackIndex();
+ fail("getSampleTrackIndex succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetTrackFormatBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.getTrackFormat(0);
+ fail("getTrackFormat succeeds before setDataSource");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetTrackFormatForInvalidIndex() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ try {
+ extractor.getTrackFormat(-1);
+ fail("getTrackFormat succeeds for track index: -1");
+ } catch (Exception e) {
+ // expected
+ }
+ try {
+ extractor.getTrackFormat(extractor.getTrackCount());
+ fail("getTrackFormat succeeds for out of bounds track index: " +
+ extractor.getTrackCount());
+ } catch (Exception e) {
+ // expected
+ }
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testGetTrackFormatAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.release();
+ try {
+ extractor.getTrackFormat(0);
+ fail("getTrackFormat succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testReadSampleDataBeforeSetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+ try {
+ assertTrue("readSampleData returns val >= 0 before setDataSource",
+ extractor.readSampleData(byteBuffer, 0) < 0);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testReadSampleDataBeforeSelectTrack() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ try {
+ assertTrue("readSampleData returns val >= 0 without any active tracks",
+ extractor.readSampleData(byteBuffer, 0) < 0);
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testIfInvalidOffsetIsRejectedByReadSampleData() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+ extractor.setDataSource(mInpPrefix + mInpMedia);
+ extractor.selectTrack(0);
+ try {
+ // readSampleData with negative offset
+ try {
+ extractor.readSampleData(byteBuffer, -1);
+ fail("readSampleData succeeds with negative offset");
+ } catch (Exception e) {
+ // expected
+ }
+
+ // readSampleData with byteBuffer's capacity - offset < frame size
+ int sampleSize = (int) extractor.getSampleSize();
+ try {
+ extractor.readSampleData(byteBuffer, MAX_SAMPLE_SIZE - sampleSize + 1);
+ fail("readSampleData succeeds when available size of byteBuffer is less than " +
+ "frame size");
+ } catch (Exception e) {
+ // expected
+ }
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testReadSampleDataAfterRelease() {
+ MediaExtractor extractor = new MediaExtractor();
+ ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+ extractor.release();
+ try {
+ extractor.readSampleData(byteBuffer, 0);
+ fail("readSampleData succeeds after release");
+ } catch (Exception e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testIfInvalidDataSourceIsRejectedBySetDataSource() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ TestMediaDataSource dataSource =
+ TestMediaDataSource.fromString(mInpPrefix + mInpMedia, true, false);
+ try {
+ try {
+ extractor.setDataSource(dataSource);
+ fail("setDataSource succeeds with malformed media data source");
+ } catch (Exception e) {
+ // expected
+ }
+ assertTrue(dataSource.isClosed());
+ dataSource = TestMediaDataSource.fromString(mInpPrefix + mInpMedia, false, true);
+
+ try {
+ extractor.setDataSource(dataSource);
+ fail("setDataSource succeeds with malformed media data source");
+ } catch (Exception e) {
+ // expected
+ }
+ } finally {
+ assertTrue(dataSource.isClosed());
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testIfNullFDIsRejectedBySetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.setDataSource((FileDescriptor) null);
+ fail("setDataSource succeeds with null fd");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testIfWriteOnlyAssetFDIsRejectedBySetDataSource() throws IOException {
+ File inpFile = File.createTempFile("ExtractorTestApisetDSAFD", ".in");
+ MediaExtractor extractor = new MediaExtractor();
+ try (ParcelFileDescriptor parcelFD = ParcelFileDescriptor
+ .open(inpFile, ParcelFileDescriptor.MODE_WRITE_ONLY);
+ AssetFileDescriptor afd = new AssetFileDescriptor(parcelFD, 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH)) {
+ extractor.setDataSource(afd);
+ fail("setDataSource succeeds write only afd");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ inpFile.delete();
+ }
+
+ @Test
+ public void testIfWriteOnlyFDIsRejectedBySetDataSource() throws IOException {
+ MediaExtractor extractor = new MediaExtractor();
+ File inpFile = File.createTempFile("ExtractorTestApisetDSFD", ".in");
+ try (FileOutputStream fOut = new FileOutputStream(inpFile)) {
+ extractor.setDataSource(fOut.getFD());
+ fail("setDataSource succeeds write only fd");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ inpFile.delete();
+ }
+
+ @Test
+ public void testIfNullMediaDataSourceIsRejectedBySetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.setDataSource((MediaDataSource) null);
+ fail("setDataSource succeeds with null data source");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testIfNullFileIsRejectedBySetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.setDataSource((String) null);
+ fail("setDataSource succeeds with null file path");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+
+ @Test
+ public void testIfNullAssetFDIsRejectedBySetDataSource() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.setDataSource((AssetFileDescriptor) null);
+ fail("setDataSource succeeds with null asset fd");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ extractor.release();
+ }
+ }
+ }
+}
diff --git a/tests/media/src/android/mediav2/cts/MuxerTest.java b/tests/media/src/android/mediav2/cts/MuxerTest.java
index b20ee08..45df10a 100644
--- a/tests/media/src/android/mediav2/cts/MuxerTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerTest.java
@@ -770,7 +770,6 @@
* Add an offset to the presentation time of samples of a track. Mux with the added offset,
* validate by re-extracting the muxer output file and compare with original.
*/
- @Ignore("TODO(test fails for mp4, need to check if test is faulty)")
@LargeTest
@RunWith(Parameterized.class)
public static class TestOffsetPts {
@@ -829,6 +828,10 @@
public void testOffsetPresentationTime() throws IOException {
final int OFFSET_TS = 111000;
Assume.assumeTrue(shouldRunTest(mOutFormat));
+ Assume.assumeTrue("TODO(b/148978457)",
+ mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ Assume.assumeTrue("TODO(b/148978457)",
+ mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
Assume.assumeTrue("TODO(b/146423022)",
mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
Assume.assumeTrue("TODO(b/146421018)",
@@ -858,6 +861,10 @@
@Test
public void testOffsetPresentationTimeNative() {
Assume.assumeTrue(shouldRunTest(mOutFormat));
+ Assume.assumeTrue("TODO(b/148978457)",
+ mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ Assume.assumeTrue("TODO(b/148978457)",
+ mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
Assume.assumeTrue("TODO(b/146423022)",
mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
Assume.assumeTrue("TODO(b/146421018)",
diff --git a/tests/media/src/android/mediav2/cts/MuxerUnitTest.java b/tests/media/src/android/mediav2/cts/MuxerUnitTest.java
new file mode 100644
index 0000000..6942393
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/MuxerUnitTest.java
@@ -0,0 +1,906 @@
+/*
+ * Copyright (C) 2019 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.mediav2.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import static android.system.Os.pipe;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests MediaMuxer API that are independent of MediaMuxer.OutputFormat. Constructors,
+ * addTrack, start, writeSampleData, stop, release are independent of OutputFormat selected.
+ * Legality of these APIs are tested in this class.
+ */
+@RunWith(Enclosed.class)
+public class MuxerUnitTest {
+ // duplicate definitions of hide fields of MediaMuxer.OutputFormat.
+ private static final int MUXER_OUTPUT_LAST = MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG;
+
+ @SmallTest
+ public static class TestApi {
+ @Rule
+ public TestName testName = new TestName();
+
+ @Before
+ public void prologue() throws IOException {
+ mOutMedia = File.createTempFile(testName.getMethodName(), ".out");
+ mOutLoc = mOutMedia.getAbsolutePath();
+ }
+
+ @After
+ public void epilogue() {
+ new File(mOutLoc).delete();
+ }
+
+ private File mOutMedia;
+ private String mOutLoc;
+
+ // Insert one frame SubRip
+ static private void insertPerFrameSubtitles(MediaMuxer muxer, long presentationTimeUs,
+ int trackID) {
+ byte[] greeting = "hello world".getBytes(StandardCharsets.UTF_8);
+ ByteBuffer metaBuff = ByteBuffer.allocate(greeting.length);
+ metaBuff.put(greeting);
+ MediaCodec.BufferInfo metaInfo = new MediaCodec.BufferInfo();
+ metaInfo.offset = 0;
+ metaInfo.size = greeting.length;
+ metaInfo.presentationTimeUs = presentationTimeUs;
+ metaInfo.flags = 0;
+ muxer.writeSampleData(trackID, metaBuff, metaInfo);
+ }
+
+ @Test
+ public void testIfNullPathIsRejected() {
+ MediaMuxer muxer = null;
+ try {
+ String nullPath = null;
+ muxer = new MediaMuxer(nullPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+ fail("null destination path accepted by constructor");
+ } catch (IllegalArgumentException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ if (null != muxer) muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfNullFdIsRejected() {
+ MediaMuxer muxer = null;
+ try {
+ FileDescriptor fd = null;
+ muxer = new MediaMuxer(fd, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+ fail("null fd accepted by constructor");
+ } catch (IllegalArgumentException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ if (null != muxer) muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfInvalidFdIsRejected() {
+ MediaMuxer muxer = null;
+ try {
+ FileDescriptor fd = new FileDescriptor();
+ muxer = new MediaMuxer(fd, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+ fail("Invalid fd accepted by constructor");
+ } catch (IllegalArgumentException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ if (null != muxer) muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfReadOnlyFdIsRejected() {
+ MediaMuxer muxer = null;
+ try (FileInputStream fInp = new FileInputStream(mOutMedia)) {
+ muxer = new MediaMuxer(fInp.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+ fail("fd with read-only attribute accepted by constructor");
+ } catch (IOException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ if (null != muxer) muxer.release();
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/146417874)")
+ public void testIfWriteOnlyFdIsRejected() {
+ MediaMuxer muxer = null;
+ try (FileOutputStream fOut = new FileOutputStream(mOutMedia)) {
+ muxer = new MediaMuxer(fOut.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
+ fail("fd with write only attribute accepted by constructor");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ if (null != muxer) muxer.release();
+ }
+ assertTrue(mOutMedia.delete());
+ }
+
+ @Test
+ @Ignore("TODO(b/146417874)")
+ public void testIfNonSeekableFdIsRejected() {
+ MediaMuxer muxer = null;
+ try {
+ FileDescriptor[] fd = pipe();
+ muxer = new MediaMuxer(fd[1], MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+ fail("pipe, a non-seekable fd accepted by constructor");
+ } catch (IOException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ if (null != muxer) muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfInvalidOutputFormatIsRejected() {
+ MediaMuxer muxer = null;
+ try {
+ muxer = new MediaMuxer(mOutLoc, MUXER_OUTPUT_LAST + 1);
+ fail("Invalid Media format accepted by constructor");
+ } catch (IllegalArgumentException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ if (null != muxer) muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfNullMediaFormatIsRejected() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ muxer.addTrack(null);
+ fail("null media format accepted by addTrack");
+ } catch (IllegalArgumentException e) {
+ // expected
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfInvalidMediaFormatIsRejected() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ // Invalid media format - no mime key
+ try {
+ muxer.addTrack(new MediaFormat());
+ fail("Invalid media format accepted by addTrack");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ // metadata mime format shall start with "application/*"
+ try {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_CEA_608);
+ muxer.addTrack(format);
+ fail("Invalid media format accepted by addTrack");
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ // Ideally check only for IllegalArgumentException.
+ // expected
+ }
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/146923138)")
+ public void testIfCorruptMediaFormatIsRejected() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+ /* TODO: Audio/Video formats, have certain keys required to be set. It is noticed
+ that even when these keys are not set, no exceptions were raised. Do we need to
+ add fixtures for those cases. */
+ try {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
+ format.setInteger(MediaFormat.KEY_SAMPLE_RATE, -1);
+ muxer.addTrack(format);
+ muxer.start();
+ fail("muxer accepts media format with required key-value pairs missing");
+ } catch (Exception e) {
+ // expected
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/146423844)")
+ public void testIfAddTrackSucceedsAfterStart() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+ muxer.addTrack(format);
+ muxer.start();
+ muxer.addTrack(format);
+ fail("muxer.addTrack() succeeded after muxer.start()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfAddTrackSucceedsAfterWriteSampleData() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ muxer.addTrack(format);
+ fail("muxer.addTrack() succeeded after muxer.writeSampleData()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfAddTrackSucceedsAfterStop() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ muxer.stop();
+ muxer.addTrack(format);
+ fail("muxer.addTrack() succeeded after muxer.stop()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfAddTrackSucceedsAfterRelease() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+ muxer.release();
+ muxer.addTrack(format);
+ fail("muxer.addTrack() succeeded after muxer.release()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfMuxerStartsBeforeAddTrack() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ try {
+ muxer.start();
+ fail("muxer.start() succeeded before muxer.addTrack()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/146423844)")
+ public void testIdempotentStart() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ try {
+ muxer.addTrack(format);
+ muxer.start();
+ muxer.start();
+ fail("muxer.start() succeeded after muxer.start()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfMuxerStartsAfterWriteSampleData() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ try {
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ muxer.start();
+ fail("muxer.start() succeeded after muxer.writeSampleData()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfMuxerStartsAfterStop() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ try {
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ muxer.stop();
+ muxer.start();
+ fail("muxer.start() succeeded after muxer.stop()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfMuxerStartsAfterRelease() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+ try {
+ muxer.release();
+ muxer.start();
+ fail("muxer.start() succeeded after muxer.release()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testStopOnANonStartedMuxer() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+ try {
+ muxer.stop();
+ fail("muxer.stop() succeeded before muxer.start()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIdempotentStop() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ try {
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ muxer.stop();
+ muxer.stop();
+ fail("muxer.stop() succeeded after muxer.stop()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testStopAfterRelease() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ muxer.release();
+ muxer.stop();
+ fail("muxer.stop() succeeded after muxer.release()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/146423844)")
+ public void testSimpleStartStopMuxer() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ try {
+ muxer.addTrack(format);
+ muxer.start();
+ muxer.stop();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfWriteSampleDataRejectsInvalidTrackIndex() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 22000, trackID);
+ try {
+ insertPerFrameSubtitles(muxer, 0, trackID - 1);
+ fail("muxer.writeSampleData() succeeded with bad argument, trackIndex");
+ } catch (IllegalArgumentException e) {
+ // expected
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfWriteSampleDataRejectsNullByteBuffer() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 22000, trackID);
+
+ MediaCodec.BufferInfo metaInfo = new MediaCodec.BufferInfo();
+ metaInfo.offset = 0;
+ metaInfo.size = 24;
+ metaInfo.presentationTimeUs = 0;
+ metaInfo.flags = 0;
+ try {
+ muxer.writeSampleData(trackID, null, metaInfo);
+ fail("muxer.writeSampleData() succeeded with bad argument, byteBuffer = null");
+ } catch (IllegalArgumentException e) {
+ // expected
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfWriteSampleDataRejectsNullBuffInfo() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 22000, trackID);
+
+ byte[] greeting = "hello world".getBytes(StandardCharsets.UTF_8);
+ ByteBuffer metaBuff = ByteBuffer.allocate(greeting.length);
+ metaBuff.put(greeting);
+
+ try {
+ muxer.writeSampleData(trackID, metaBuff, null);
+ fail("muxer.writeSampleData() succeeded with bad argument, byteBuffer = null");
+ } catch (IllegalArgumentException e) {
+ // expected
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfWriteSampleDataRejectsInvalidBuffInfo() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 22000, trackID);
+
+ byte[] greeting = "hello world".getBytes(StandardCharsets.UTF_8);
+ ByteBuffer metaBuff = ByteBuffer.allocate(greeting.length);
+ metaBuff.put(greeting);
+
+ MediaCodec.BufferInfo metaInfo = new MediaCodec.BufferInfo();
+
+ try {
+ // invalid metaData : buffer offset < 0
+ try {
+ metaInfo.offset = -1;
+ metaInfo.size = greeting.length;
+ metaInfo.presentationTimeUs = 0;
+ metaInfo.flags = 0;
+ muxer.writeSampleData(trackID, metaBuff, metaInfo);
+ fail("muxer.writeSampleData() succeeded with bad argument, bufInfo.offset < 0");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ // invalid metaData : buffer size < 0
+ try {
+ metaInfo.offset = 0;
+ metaInfo.size = -1;
+ metaInfo.presentationTimeUs = 0;
+ metaInfo.flags = 0;
+ muxer.writeSampleData(trackID, metaBuff, metaInfo);
+ fail("muxer.writeSampleData() succeeded with bad argument, buffInfo.size < 0");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ // invalid metaData : buffer size > capacity
+ try {
+ metaInfo.offset = 0;
+ metaInfo.size = greeting.length * 2;
+ metaInfo.presentationTimeUs = 0;
+ metaInfo.flags = 0;
+ muxer.writeSampleData(trackID, metaBuff, metaInfo);
+ fail("muxer.writeSampleData() succeeded with bad argument, buffInfo.size > " +
+ "byteBuffer.capacity()");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ // invalid metaData : buffer offset + size > capacity
+ try {
+ metaInfo.offset = 1;
+ metaInfo.size = greeting.length;
+ metaInfo.presentationTimeUs = 0;
+ metaInfo.flags = 0;
+ muxer.writeSampleData(trackID, metaBuff, metaInfo);
+ fail("muxer.writeSampleData() succeeded with bad argument, bufferInfo.offset " +
+ "+ bufferInfo.size > byteBuffer.capacity()");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/147128377)")
+ public void testIfWriteSampleDataRejectsInvalidPts() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 22000, trackID);
+ try {
+ insertPerFrameSubtitles(muxer, -33000, trackID);
+ fail("muxer.writeSampleData() succeeded with bad argument, presentationTime");
+ } catch (IllegalArgumentException e) {
+ // expected
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfWriteSampleDataSucceedsBeforeStart() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ try {
+ int trackID = muxer.addTrack(format);
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ fail("muxer.WriteSampleData() succeeds before muxer.start()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfWriteSampleDataSucceedsAfterStop() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ try {
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ muxer.stop();
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ fail("muxer.WriteSampleData() succeeds after muxer.stop()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIfWriteSampleDataSucceedsAfterRelease() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+ try {
+ int trackID = muxer.addTrack(format);
+ muxer.start();
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ muxer.release();
+ insertPerFrameSubtitles(muxer, 0, trackID);
+ fail("muxer.WriteSampleData() succeeds after muxer.release()");
+ } catch (IllegalStateException e) {
+ // expected
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ muxer.release();
+ }
+ }
+
+ @Test
+ public void testIdempotentRelease() throws IOException {
+ MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ muxer.release();
+ muxer.release();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+ }
+
+ @SmallTest
+ public static class TestApiNative {
+ @Rule
+ public TestName testName = new TestName();
+
+ static {
+ System.loadLibrary("ctsmediav2muxer_jni");
+ }
+
+ @Before
+ public void prologue() throws IOException {
+ File mOutMedia = File.createTempFile(testName.getMethodName(), ".out");
+ mOutLoc = mOutMedia.getAbsolutePath();
+ }
+
+ @After
+ public void epilogue() {
+ new File(mOutLoc).delete();
+ }
+
+ private String mOutLoc;
+
+ private native boolean nativeTestIfInvalidFdIsRejected();
+ private native boolean nativeTestIfReadOnlyFdIsRejected(String outPath);
+ private native boolean nativeTestIfWriteOnlyFdIsRejected(String outPath);
+ private native boolean nativeTestIfNonSeekableFdIsRejected(String outPath);
+ private native boolean nativeTestIfInvalidOutputFormatIsRejected(String outPath);
+
+ private native boolean nativeTestIfInvalidMediaFormatIsRejected(String outPath);
+ private native boolean nativeTestIfCorruptMediaFormatIsRejected(String outPath);
+ private native boolean nativeTestIfAddTrackSucceedsAfterStart(String outPath);
+ private native boolean nativeTestIfAddTrackSucceedsAfterWriteSampleData(String outPath);
+ private native boolean nativeTestIfAddTrackSucceedsAfterStop(String outPath);
+
+ private native boolean nativeTestIfMuxerStartsBeforeAddTrack(String outPath);
+ private native boolean nativeTestIdempotentStart(String outPath);
+ private native boolean nativeTestIfMuxerStartsAfterWriteSampleData(String outPath);
+ private native boolean nativeTestIfMuxerStartsAfterStop(String outPath);
+
+ private native boolean nativeTestStopOnANonStartedMuxer(String outPath);
+ private native boolean nativeTestIdempotentStop(String outPath);
+ private native boolean nativeTestSimpleStartStop(String outPath);
+
+ private native boolean nativeTestIfWriteSampleDataRejectsInvalidTrackIndex(String outPath);
+ private native boolean nativeTestIfWriteSampleDataRejectsInvalidPts(String outPath);
+ private native boolean nativeTestIfWriteSampleDataSucceedsBeforeStart(String outPath);
+ private native boolean nativeTestIfWriteSampleDataSucceedsAfterStop(String outPath);
+
+ @Test
+ @Ignore("TODO(b/146417874)")
+ public void testIfInvalidFdIsRejected() {
+ assertTrue(nativeTestIfInvalidFdIsRejected());
+ }
+
+ @Test
+ @Ignore("TODO(b/146417874)")
+ public void testIfReadOnlyFdIsRejected() {
+ assertTrue(nativeTestIfReadOnlyFdIsRejected(mOutLoc));
+ }
+
+ @Test
+ @Ignore("TODO(b/146417874)")
+ public void testIfWriteOnlyFdIsRejected() {
+ assertTrue(nativeTestIfWriteOnlyFdIsRejected(mOutLoc));
+ }
+
+ @Test
+ @Ignore("TODO(b/146417874)")
+ public void testIfNonSeekableFdIsRejected() {
+ assertTrue(nativeTestIfNonSeekableFdIsRejected(mOutLoc));
+ }
+
+ @Test
+ @Ignore("TODO(b/146417874)")
+ public void testIfInvalidOutputFormatIsRejected() {
+ assertTrue(nativeTestIfInvalidOutputFormatIsRejected(mOutLoc));
+ }
+
+ @Test
+ public void testIfInvalidMediaFormatIsRejected() {
+ assertTrue(nativeTestIfInvalidMediaFormatIsRejected(mOutLoc));
+ }
+
+ @Test
+ @Ignore("TODO(b/146923138)")
+ public void testIfCorruptMediaFormatIsRejected() {
+ assertTrue(nativeTestIfCorruptMediaFormatIsRejected(mOutLoc));
+ }
+
+ @Test
+ public void testIfAddTrackSucceedsAfterStart() {
+ assertTrue(nativeTestIfAddTrackSucceedsAfterStart(mOutLoc));
+ }
+
+ @Test
+ public void testIfAddTrackSucceedsAfterWriteSampleData() {
+ assertTrue(nativeTestIfAddTrackSucceedsAfterWriteSampleData(mOutLoc));
+ }
+
+ @Test
+ public void testIfAddTrackSucceedsAfterStop() {
+ assertTrue(nativeTestIfAddTrackSucceedsAfterStop(mOutLoc));
+ }
+
+ @Test
+ public void testIfMuxerStartsBeforeAddTrack() {
+ assertTrue(nativeTestIfMuxerStartsBeforeAddTrack(mOutLoc));
+ }
+
+ @Test
+ public void testIdempotentStart() {
+ assertTrue(nativeTestIdempotentStart(mOutLoc));
+ }
+
+ @Test
+ public void testIfMuxerStartsAfterWriteSampleData() {
+ assertTrue(nativeTestIfMuxerStartsAfterWriteSampleData(mOutLoc));
+ }
+
+ @Test
+ public void testIfMuxerStartsAfterStop() {
+ assertTrue(nativeTestIfMuxerStartsAfterStop(mOutLoc));
+ }
+
+ @Test
+ public void testStopOnANonStartedMuxer() {
+ assertTrue(nativeTestStopOnANonStartedMuxer(mOutLoc));
+ }
+
+ @Test
+ public void testIdempotentStop() {
+ assertTrue(nativeTestIdempotentStop(mOutLoc));
+ }
+
+ @Test
+ @Ignore("TODO(b/146423844)")
+ public void testSimpleStartStopMuxer() {
+ assertTrue(nativeTestSimpleStartStop(mOutLoc));
+ }
+
+ @Test
+ public void testIfWriteSampleDataRejectsInvalidTrackIndex() {
+ assertTrue(nativeTestIfWriteSampleDataRejectsInvalidTrackIndex(mOutLoc));
+ }
+
+ @Test
+ @Ignore("TODO(b/147128377)")
+ public void testIfWriteSampleDataRejectsInvalidPts() {
+ assertTrue(nativeTestIfWriteSampleDataRejectsInvalidPts(mOutLoc));
+ }
+
+ @Test
+ public void testIfWriteSampleDataSucceedsBeforeStart() {
+ assertTrue(nativeTestIfWriteSampleDataSucceedsBeforeStart(mOutLoc));
+ }
+
+ @Test
+ public void testIfWriteSampleDataSucceedsAfterStop() {
+ assertTrue(nativeTestIfWriteSampleDataSucceedsAfterStop(mOutLoc));
+ }
+ }
+}
diff --git a/tests/signature/lib/common/src/android/signature/cts/DexMethod.java b/tests/signature/lib/common/src/android/signature/cts/DexMethod.java
index dfe1934..967a5a8 100644
--- a/tests/signature/lib/common/src/android/signature/cts/DexMethod.java
+++ b/tests/signature/lib/common/src/android/signature/cts/DexMethod.java
@@ -23,10 +23,14 @@
public class DexMethod extends DexMember {
private final List<String> mParamTypeList;
- private static final Pattern REGEX_SIGNATURE = Pattern.compile("^\\((.*)\\)(.*)$");
+
+ private enum ParseType {
+ DEX_TYPE_LIST,
+ DEX_RETURN_TYPE,
+ }
public DexMethod(String className, String name, String signature, String[] flags) {
- super(className, name, parseDexReturnType(signature), flags);
+ super(className, name, parseSignature(signature, ParseType.DEX_RETURN_TYPE), flags);
mParamTypeList = parseDexTypeList(signature);
}
@@ -52,20 +56,24 @@
+ "(" + String.join(", ", getJavaParameterTypes()) + ")";
}
- private static Matcher matchSignature(String signature) {
- Matcher m = REGEX_SIGNATURE.matcher(signature);
- if (!m.matches()) {
+ private static String parseSignature(String signature, ParseType type) {
+ // Form of signature:
+ // (DexTypeList)DexReturnType
+ int length = signature.length();
+ int paren = signature.indexOf(')', 1);
+ if (length < 2 || signature.charAt(0) != '(' || paren == -1) {
throw new RuntimeException("Could not parse method signature: " + signature);
}
- return m;
- }
-
- private static String parseDexReturnType(String signature) {
- return matchSignature(signature).group(2);
+ if (type == ParseType.DEX_TYPE_LIST) {
+ return signature.substring(1, paren);
+ } else {
+ // ParseType.DEX_RETURN_TYPE
+ return signature.substring(paren + 1, length);
+ }
}
private static List<String> parseDexTypeList(String signature) {
- String typeSequence = matchSignature(signature).group(1);
+ String typeSequence = parseSignature(signature, ParseType.DEX_TYPE_LIST);
List<String> list = new ArrayList<String>();
while (!typeSequence.isEmpty()) {
String type = firstDexTypeFromList(typeSequence);
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/.hash b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/.hash
new file mode 100644
index 0000000..9c76fef
--- /dev/null
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/.hash
@@ -0,0 +1 @@
+8d903ce236a40b41624907c4d1d7a651eca9f763 -
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/.hash b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/.hash
new file mode 100644
index 0000000..ad2b69c3
--- /dev/null
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/.hash
@@ -0,0 +1 @@
+8359746d4317c0ef0f014821d362c4cfe96e2166 -
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
index 1b31a8c..cee0944 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
@@ -803,6 +803,17 @@
}
}
+TEST_P(NdkBinderTest_Aidl, GetInterfaceHash) {
+ std::string res;
+ EXPECT_OK(iface->getInterfaceHash(&res));
+ if (GetParam().shouldBeOld) {
+ // aidl_api/libbinder_ndk_test_interface/1/.hash
+ EXPECT_EQ("8d903ce236a40b41624907c4d1d7a651eca9f763", res);
+ } else {
+ EXPECT_EQ("notfrozen", res);
+ }
+}
+
std::shared_ptr<ITest> getProxyLocalService() {
std::shared_ptr<MyTest> test = SharedRefBase::make<MyTest>();
SpAIBinder binder = test->asBinder();
@@ -820,7 +831,7 @@
//
// Warning: for testing purposes only. This parcels things within the same process for testing
// purposes. In normal usage, this should just return SharedRefBase::make<MyTest> directly.
- return (new BpTest(binder))->ref<ITest>();
+ return SharedRefBase::make<BpTest>(binder);
}
std::shared_ptr<ITest> getNdkBinderTestJavaService(const std::string& method) {
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
index 21c40c7..814e1e4 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
@@ -177,6 +177,9 @@
private static class Empty extends IEmpty.Stub {
@Override
public int getInterfaceVersion() { return Empty.VERSION; }
+
+ @Override
+ public String getInterfaceHash() { return Empty.HASH; }
}
@Test
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java b/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
index 872c166..7b60483 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
@@ -37,6 +37,9 @@
public int getInterfaceVersion() { return TestImpl.VERSION; }
@Override
+ public String getInterfaceHash() { return TestImpl.HASH; }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
for (String arg : args) {
pw.print(arg);
diff --git a/tests/tests/bionic/Android.build.copy.libs.mk b/tests/tests/bionic/Android.build.copy.libs.mk
index 5867547..8900a75 100644
--- a/tests/tests/bionic/Android.build.copy.libs.mk
+++ b/tests/tests/bionic/Android.build.copy.libs.mk
@@ -44,8 +44,13 @@
libdlext_test_zip/libdlext_test_zip.so \
libdlext_test_zip/libdlext_test_zip_zipaligned.zip \
libgnu-hash-table-library.so \
- librelr-new.so \
- librelr-old.so \
+ libns_hidden_child_global.so \
+ libns_hidden_child_internal.so \
+ libns_hidden_child_public.so \
+ librelocations-fat.so \
+ librelocations-ANDROID_RELR.so \
+ librelocations-ANDROID_REL.so \
+ librelocations-RELR.so \
libsegment_gap_inner.so \
libsegment_gap_outer.so \
libsysv-hash-table-library.so \
@@ -133,6 +138,8 @@
ns_a/libnstest_ns_a_public1_internal.so \
ns_b/libnstest_ns_b_public2.so \
ns_b/libnstest_ns_b_public3.so \
+ ns_hidden_child_app/libns_hidden_child_app.so \
+ ns_hidden_child_helper/ns_hidden_child_helper \
prebuilt-elf-files/libtest_invalid-empty_shdr_table.so \
prebuilt-elf-files/libtest_invalid-rw_load_segment.so \
prebuilt-elf-files/libtest_invalid-textrels.so \
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
index bee4729..b3bc692 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
@@ -74,7 +74,7 @@
@Override
public void setUp() {
- if (!isBleSupported()) {
+ if (!TestUtils.isBleSupported(getContext())) {
return;
}
BluetoothManager manager = (BluetoothManager) mContext.getSystemService(
@@ -84,7 +84,7 @@
// Note it's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and check
// bluetooth state.
mBluetoothAdapter.enable();
- sleep(ADAPTER_ENABLE_TIMEOUT);
+ TestUtils.sleep(ADAPTER_ENABLE_TIMEOUT);
}
mScanner = mBluetoothAdapter.getBluetoothLeScanner();
mLocationOn = TestUtils.isLocationOn(getContext());
@@ -97,7 +97,7 @@
@Override
public void tearDown() {
- if (!isBleSupported()) {
+ if (!TestUtils.isBleSupported(getContext())) {
// mBluetoothAdapter == null.
return;
}
@@ -106,7 +106,7 @@
TestUtils.disableLocation(getContext());
}
mBluetoothAdapter.disable();
- sleep(ADAPTER_ENABLE_TIMEOUT);
+ TestUtils.sleep(ADAPTER_ENABLE_TIMEOUT);
}
/**
@@ -114,7 +114,7 @@
*/
@MediumTest
public void testBasicBleScan() {
- if (!isBleSupported()) {
+ if (!TestUtils.isBleSupported(getContext())) {
return;
}
long scanStartMillis = SystemClock.elapsedRealtime();
@@ -131,7 +131,7 @@
*/
@MediumTest
public void testScanFilter() {
- if (!isBleSupported()) {
+ if (!TestUtils.isBleSupported(getContext())) {
return;
}
@@ -147,9 +147,9 @@
ScanSettings settings = new ScanSettings.Builder().setScanMode(
ScanSettings.SCAN_MODE_LOW_LATENCY).build();
mScanner.startScan(filters, settings, filterLeScanCallback);
- sleep(SCAN_DURATION_MILLIS);
+ TestUtils.sleep(SCAN_DURATION_MILLIS);
mScanner.stopScan(filterLeScanCallback);
- sleep(SCAN_STOP_TIMEOUT);
+ TestUtils.sleep(SCAN_STOP_TIMEOUT);
Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults();
for (ScanResult result : scanResults) {
assertTrue(filter.matches(result));
@@ -195,7 +195,7 @@
// */
// @MediumTest
// public void testOpportunisticScan() {
-// if (!isBleSupported()) {
+// if (!TestUtils.isBleSupported(getContext())) {
// return;
// }
// ScanSettings opportunisticScanSettings = new ScanSettings.Builder()
@@ -245,7 +245,7 @@
*/
@MediumTest
public void testBatchScan() {
- if (!isBleSupported() || !isBleBatchScanSupported()) {
+ if (!TestUtils.isBleSupported(getContext()) || !isBleBatchScanSupported()) {
Log.d(TAG, "BLE or BLE batching not suppported");
return;
}
@@ -255,7 +255,7 @@
BleScanCallback batchScanCallback = new BleScanCallback();
mScanner.startScan(Collections.<ScanFilter>emptyList(), batchScanSettings,
batchScanCallback);
- sleep(SCAN_DURATION_MILLIS);
+ TestUtils.sleep(SCAN_DURATION_MILLIS);
mScanner.flushPendingScanResults(batchScanCallback);
mFlushBatchScanLatch = new CountDownLatch(1);
List<ScanResult> results = batchScanCallback.getBatchScanResults();
@@ -276,7 +276,7 @@
*/
@MediumTest
public void testStartScanPendingIntent_nullnull() throws Exception {
- if (!isBleSupported() || !isBleBatchScanSupported()) {
+ if (!TestUtils.isBleSupported(getContext()) || !isBleBatchScanSupported()) {
Log.d(TAG, "BLE or BLE batching not suppported");
return;
}
@@ -295,7 +295,7 @@
*/
@MediumTest
public void testStartScanPendingIntent() throws Exception {
- if (!isBleSupported() || !isBleBatchScanSupported()) {
+ if (!TestUtils.isBleSupported(getContext()) || !isBleBatchScanSupported()) {
Log.d(TAG, "BLE or BLE batching not suppported");
return;
}
@@ -384,27 +384,12 @@
private Set<ScanResult> scan() {
BleScanCallback regularLeScanCallback = new BleScanCallback();
mScanner.startScan(regularLeScanCallback);
- sleep(SCAN_DURATION_MILLIS);
+ TestUtils.sleep(SCAN_DURATION_MILLIS);
mScanner.stopScan(regularLeScanCallback);
- sleep(SCAN_STOP_TIMEOUT);
+ TestUtils.sleep(SCAN_STOP_TIMEOUT);
return regularLeScanCallback.getScanResults();
}
- // Put the current thread to sleep.
- private void sleep(int sleepMillis) {
- try {
- Thread.sleep(sleepMillis);
- } catch (InterruptedException e) {
- Log.e(TAG, "interrupted", e);
- }
- }
-
- // Check if Bluetooth LE feature is supported on DUT.
- private boolean isBleSupported() {
- return getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
- }
-
// Returns whether offloaded scan batching is supported.
private boolean isBleBatchScanSupported() {
return mBluetoothAdapter.isOffloadedScanBatchingSupported();
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
new file mode 100644
index 0000000..433a39f
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2020 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.bluetooth.cts;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothServerSocket;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.IOException;
+
+public class LeL2capSocketTest extends AndroidTestCase {
+
+ private static final int NUM_ITERATIONS_FOR_REPEATED_TEST = 100;
+ private static final int ADAPTER_TOGGLE_TIMEOUT_MS = 4000;
+
+ private BluetoothAdapter mAdapter = null;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ if (!TestUtils.isBleSupported(getContext())) {
+ return;
+ }
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ assertNotNull("BluetoothAdapter.getDefaultAdapter() returned null. "
+ + "Does this device have a Bluetooth adapter?", mAdapter);
+ if (!mAdapter.isEnabled()) {
+ // Note: It's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and
+ // check bluetooth state per comments from BluetoothLeScanTest.java
+ mAdapter.enable();
+ TestUtils.sleep(ADAPTER_TOGGLE_TIMEOUT_MS);
+ }
+ assertTrue("Bluetooth failed to be enabled", mAdapter.isEnabled());
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (!TestUtils.isBleSupported(getContext())) {
+ return;
+ }
+ mAdapter.disable();
+ TestUtils.sleep(ADAPTER_TOGGLE_TIMEOUT_MS);
+ assertFalse("Bluetooth failed to be disabled", mAdapter.isEnabled());
+ mAdapter = null;
+ super.tearDown();
+ }
+
+
+ @SmallTest
+ public void testOpenInsecureLeL2capServerSocketOnce() {
+ if (!TestUtils.isBleSupported(getContext())) {
+ return;
+ }
+ assertTrue("Bluetooth is not enabled", mAdapter.isEnabled());
+ try {
+ final BluetoothServerSocket serverSocket = mAdapter.listenUsingInsecureL2capChannel();
+ assertNotNull("Failed to get server socket", serverSocket);
+ serverSocket.close();
+ } catch (IOException exp) {
+ fail("IOException while opening and closing server socket: " + exp);
+ }
+ }
+
+ @SmallTest
+ public void testOpenInsecureLeL2capServerSocketRepeatedly() {
+ if (!TestUtils.isBleSupported(getContext())) {
+ return;
+ }
+ assertTrue("Bluetooth is not enabled", mAdapter.isEnabled());
+ try {
+ for (int i = 0; i < NUM_ITERATIONS_FOR_REPEATED_TEST; i++) {
+ final BluetoothServerSocket serverSocket =
+ mAdapter.listenUsingInsecureL2capChannel();
+ assertNotNull("Failed to get server socket", serverSocket);
+ serverSocket.close();
+ }
+ } catch (IOException exp) {
+ fail("IOException while opening and closing server socket: " + exp);
+ }
+ }
+
+ @SmallTest
+ public void testOpenSecureLeL2capServerSocketOnce() {
+ if (!TestUtils.isBleSupported(getContext())) {
+ return;
+ }
+ assertTrue("Bluetooth is not enabled", mAdapter.isEnabled());
+ try {
+ final BluetoothServerSocket serverSocket = mAdapter.listenUsingL2capChannel();
+ assertNotNull("Failed to get server socket", serverSocket);
+ serverSocket.close();
+ } catch (IOException exp) {
+ fail("IOException while opening and closing server socket: " + exp);
+ }
+ }
+
+ @SmallTest
+ public void testOpenSecureLeL2capServerSocketRepeatedly() {
+ if (!TestUtils.isBleSupported(getContext())) {
+ return;
+ }
+ assertTrue("Bluetooth is not enabled", mAdapter.isEnabled());
+ try {
+ for (int i = 0; i < NUM_ITERATIONS_FOR_REPEATED_TEST; i++) {
+ final BluetoothServerSocket serverSocket = mAdapter.listenUsingL2capChannel();
+ assertNotNull("Failed to get server socket", serverSocket);
+ serverSocket.close();
+ }
+ } catch (IOException exp) {
+ fail("IOException while opening and closing server socket: " + exp);
+ }
+ }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
index 327a2b8..342266f 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
@@ -18,7 +18,9 @@
import android.bluetooth.le.ScanRecord;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.provider.Settings;
+import android.util.Log;
import junit.framework.Assert;
@@ -91,4 +93,25 @@
static void disableLocation(Context context) {
setLocationMode(context, Settings.Secure.LOCATION_MODE_OFF);
}
+
+ /**
+ * Check if BLE is supported by this platform
+ * @param context current device context
+ * @return true if BLE is supported, false otherwise
+ */
+ static boolean isBleSupported(Context context) {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+ }
+
+ /**
+ * Put the current thread to sleep.
+ * @param sleepMillis number of milliseconds to sleep for
+ */
+ static void sleep(int sleepMillis) {
+ try {
+ Thread.sleep(sleepMillis);
+ } catch (InterruptedException e) {
+ Log.e(TestUtils.class.getSimpleName(), "interrupted", e);
+ }
+ }
}
\ No newline at end of file
diff --git a/tests/tests/dreams/OWNERS b/tests/tests/dreams/OWNERS
index 635efda..f70efe9 100644
--- a/tests/tests/dreams/OWNERS
+++ b/tests/tests/dreams/OWNERS
@@ -1,3 +1,3 @@
-# Bug component: 136515
+# Bug component: 345010
michaelwr@google.com
santoscordon@google.com
diff --git a/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java b/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
new file mode 100755
index 0000000..a30ca46
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2020 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.hardware.lights.cts.tests;
+
+import static android.hardware.lights.LightsRequest.Builder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
+
+import androidx.annotation.ColorInt;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LightsManagerTest {
+
+ private static final @ColorInt int TAN = 0xffd2b48c;
+ private static final @ColorInt int RED = 0xffff0000;
+
+ private LightsManager mManager;
+ private List<Light> mLights;
+
+ @Before
+ public void setUp() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(
+ android.Manifest.permission.CONTROL_DEVICE_LIGHTS);
+
+ final Context context = InstrumentationRegistry.getTargetContext();
+ mManager = context.getSystemService(LightsManager.class);
+ mLights = mManager.getLights();
+ }
+
+ @After
+ public void tearDown() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testControlLightsPermissionIsRequiredToUseLights() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ try {
+ mManager.getLights();
+ fail("Expected SecurityException to be thrown for getLights()");
+ } catch (SecurityException expected) {
+ }
+
+ try (LightsManager.LightsSession session = mManager.openSession()) {
+ fail("Expected SecurityException to be thrown for openSession()");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void testControlSingleLight() {
+ assumeTrue(mLights.size() >= 1);
+
+ try (LightsManager.LightsSession session = mManager.openSession()) {
+ // When the session requests to turn a single light on:
+ session.setLights(new Builder()
+ .setLight(mLights.get(0), new LightState(RED))
+ .build());
+
+ // Then the light should turn on.
+ assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(RED);
+ }
+ }
+
+ @Test
+ public void testControlMultipleLights() {
+ assumeTrue(mLights.size() >= 2);
+
+ try (LightsManager.LightsSession session = mManager.openSession()) {
+ // When the session requests to turn two of the lights on:
+ session.setLights(new Builder()
+ .setLight(mLights.get(0), new LightState(0xffaaaaff))
+ .setLight(mLights.get(1), new LightState(0xffbbbbff))
+ .build());
+
+ // Then both should turn on.
+ assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0xffaaaaff);
+ assertThat(mManager.getLightState(mLights.get(1)).getColor()).isEqualTo(0xffbbbbff);
+
+ // Any others should remain off.
+ for (int i = 2; i < mLights.size(); i++) {
+ assertThat(mManager.getLightState(mLights.get(i)).getColor()).isEqualTo(0x00);
+ }
+ }
+ }
+
+ @Test
+ public void testControlLights_onlyEffectiveForLifetimeOfClient() {
+ assumeTrue(mLights.size() >= 1);
+
+ // The light should begin by being off.
+ assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0x00);
+
+ try (LightsManager.LightsSession session = mManager.openSession()) {
+ // When a session commits changes:
+ session.setLights(new Builder().setLight(mLights.get(0), new LightState(TAN)).build());
+ // Then the light should turn on.
+ assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(TAN);
+
+ // When the session goes away:
+ session.close();
+ // Then the light should turn off.
+ assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0x00);
+ }
+ }
+
+ @Test
+ public void testControlLights_firstCallerWinsContention() {
+ assumeTrue(mLights.size() >= 1);
+
+ try (LightsManager.LightsSession session1 = mManager.openSession();
+ LightsManager.LightsSession session2 = mManager.openSession()) {
+
+ // When session1 and session2 both request the same light:
+ session1.setLights(new Builder().setLight(mLights.get(0), new LightState(TAN)).build());
+ session2.setLights(new Builder().setLight(mLights.get(0), new LightState(RED)).build());
+ // Then session1 should win because it was created first.
+ assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(TAN);
+
+ // When session1 goes away:
+ session1.close();
+ // Then session2 should have its request go into effect.
+ assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(RED);
+
+ // When session2 goes away:
+ session2.close();
+ // Then the light should turn off because there are no more sessions.
+ assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0);
+ }
+ }
+
+ @Test
+ public void testClearLight() {
+ assumeTrue(mLights.size() >= 1);
+
+ try (LightsManager.LightsSession session = mManager.openSession()) {
+ // When the session turns a light on:
+ session.setLights(new Builder().setLight(mLights.get(0), new LightState(RED)).build());
+ // And then the session clears it again:
+ session.setLights(new Builder().clearLight(mLights.get(0)).build());
+ // Then the light should turn back off.
+ assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0);
+ }
+ }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java b/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java
new file mode 100644
index 0000000..34b0cf2
--- /dev/null
+++ b/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2020 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.identity.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+
+import android.security.identity.IdentityCredential;
+import android.security.identity.IdentityCredentialStore;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.Ignore;
+
+import com.google.common.primitives.Bytes;
+
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import java.util.Optional;
+
+public class AttestationTest {
+ private static final String TAG = "AttestationTest";
+
+ // The subset of Keymaster tags we care about. See
+ //
+ // https://source.android.com/security/keystore/tags
+ //
+ // for a list of known tags.
+ //
+ public static final int KM_TAG_ATTESTATION_APPLICATION_ID = 709;
+ public static final int KM_TAG_IDENTITY_CREDENTIAL_KEY = 721;
+
+ @Ignore("Not ready until SW implementation produces correct attestations")
+ @Test
+ public void attestationTest() throws Exception {
+ Context appContext = InstrumentationRegistry.getTargetContext();
+ IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+
+ // Use a random challenge of length between 16 and 32.
+ SecureRandom random = new SecureRandom();
+ int challengeLength = 16 + random.nextInt(16);
+ byte[] challenge = new byte[challengeLength];
+ random.nextBytes(challenge);
+
+ String credentialName = "test";
+
+ store.deleteCredentialByName(credentialName);
+ Collection<X509Certificate> certChain = ProvisioningTest.createCredentialWithChallenge(
+ store, credentialName, challenge);
+ store.deleteCredentialByName("test");
+
+ // Check that each certificate in the chain isn't expired and also that it's signed by the
+ // next one.
+ assertTrue(verifyCertificateChain(certChain));
+
+ // Parse the attestation record... Identity Credential uses standard KeyMaster-style
+ // attestation with only a few differences:
+ //
+ // - Tag::IDENTITY_CREDENTIAL_KEY (boolean) must be set to identify the key as an Identity
+ // Credential key.
+ //
+ // - the KeyMaster version and security-level should be set to that of Identity Credential
+ //
+ // In this test we only test for these two things and a) the attestationApplicationId
+ // matches the calling application; and b) the given challenge matches what was sent.
+ //
+ // We could test for all the other expected attestation records but currently we assume
+ // this is already tested by Keymaster/Android Keystore tests since the TA is expected to
+ // re-use the same infrastructure.
+ //
+ X509Certificate cert = certChain.iterator().next();
+ ParsedAttestationRecord record = new ParsedAttestationRecord(cert);
+
+ // Need at least attestation version 3 and attestations to be done in either the TEE
+ // or in a StrongBox.
+ assertTrue(record.getAttestationVersion() >= 3);
+
+ // Also per the IC HAL, the keymasterSecurityLevel field is used as the securityLevel of
+ // the IC TA and this must be either TEE or in a StrongBox... except we specifically
+ // allow our software implementation to report Software here.
+ //
+ // Since we cannot get the implementation name or author at this layer, we can't test for
+ // it. This can be tested for in the VTS test, however.
+
+ // As per the IC HAL, the keymasterVersion field should be the version of the Identity
+ // Credential HAL - 1.0 - and this is encoded as major*10 + minor. This field is used by
+ // Keymaster which is known to report integers less than or equal to 4 (for KM up to 4.0)
+ // and integers greater or equal than 41 (for KM starting with 4.1).
+ //
+ // Since we won't get to version 4.0 of the IC HAL for a while, let's also check that a KM
+ // version isn't errornously returned.
+ assertTrue(record.getKeymasterVersion() >= 10);
+ assertTrue(record.getKeymasterVersion() < 40);
+
+ // Check that the challenge we passed in, is in fact in the attestation record.
+ assertArrayEquals(challenge, record.getAttestationChallenge());
+
+ // Tag::ATTESTATION_APPLICATION_ID is used to identify the set of possible applications of
+ // which one has initiated a key attestation. This is complicated ASN.1 which we don't want
+ // to parse and we don't need to [1].. we can however easily check that our applicationId
+ // is appearing as a substring somewhere in this blob.
+ //
+ // [1] : and the point of this test isn't to verify that attestations are done correctly,
+ // that's tested elsewhere in e.g. KM and Android Keystore.
+ //
+ Optional<byte[]> attestationApplicationId =
+ record.getSoftwareAuthorizationByteString(KM_TAG_ATTESTATION_APPLICATION_ID);
+ assertTrue(attestationApplicationId.isPresent());
+ String appId = appContext.getPackageName();
+ assertTrue(Bytes.indexOf(attestationApplicationId.get(), appId.getBytes()) != -1);
+
+ // Tag::IDENTITY_CREDENTIAL_KEY is used in attestations produced by the Identity Credential
+ // HAL when that HAL attests to Credential Keys.
+ boolean isIdentityCredentialKey =
+ record.getTeeAuthorizationBoolean(KM_TAG_IDENTITY_CREDENTIAL_KEY);
+ assertTrue(isIdentityCredentialKey);
+ }
+
+ // This only verifies each cert hasn't expired and signed by the next one.
+ private static boolean verifyCertificateChain(Collection<X509Certificate> certChain) {
+ X509Certificate[] certs = new X509Certificate[certChain.size()];
+ certs = certChain.toArray(certs);
+ X509Certificate parent = certs[certs.length - 1];
+ for (int i = certs.length - 1; i >= 0; i--) {
+ X509Certificate cert = certs[i];
+ try {
+ cert.checkValidity();
+ cert.verify(parent.getPublicKey());
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ parent = cert;
+ }
+ return true;
+ }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java b/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
index 6d11c7b..ac34c36 100644
--- a/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
@@ -61,6 +62,9 @@
public void dynamicAuthTest() throws Exception {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
String credentialName = "test";
diff --git a/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java b/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java
index 29e7311..a474729 100644
--- a/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.security.keystore.KeyProperties;
@@ -61,6 +62,9 @@
public void createEphemeralKey() throws IdentityCredentialException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
String credentialName = "ephemeralKeyTest";
@@ -131,8 +135,8 @@
PublicKey holderEphemeralPublicKey) throws IdentityCredentialException {
mCipherSuite = cipherSuite;
mHolderEphemeralPublicKey = holderEphemeralPublicKey;
- mCounter = 0;
- mMdlExpectedCounter = 0;
+ mCounter = 1;
+ mMdlExpectedCounter = 1;
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC);
diff --git a/tests/tests/identity/src/android/security/identity/cts/ParsedAttestationRecord.java b/tests/tests/identity/src/android/security/identity/cts/ParsedAttestationRecord.java
new file mode 100644
index 0000000..e4297d7
--- /dev/null
+++ b/tests/tests/identity/src/android/security/identity/cts/ParsedAttestationRecord.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2020 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.identity.cts;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Optional;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DEROctetString;
+
+// This code is loosely based on https://github.com/google/android-key-attestation
+
+class ParsedAttestationRecord {
+ private static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
+
+ enum SecurityLevel {
+ SOFTWARE,
+ TRUSTED_ENVIRONMENT,
+ STRONG_BOX
+ }
+
+ private static final int ATTESTATION_VERSION_INDEX = 0;
+ private static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1;
+ private static final int KEYMASTER_VERSION_INDEX = 2;
+ private static final int KEYMASTER_SECURITY_LEVEL_INDEX = 3;
+ private static final int ATTESTATION_CHALLENGE_INDEX = 4;
+ private static final int UNIQUE_ID_INDEX = 5;
+ private static final int SW_ENFORCED_INDEX = 6;
+ private static final int TEE_ENFORCED_INDEX = 7;
+
+ // Some security values. The complete list is in this AOSP file:
+ // hardware/libhardware/include/hardware/keymaster_defs.h
+ private static final int KM_SECURITY_LEVEL_SOFTWARE = 0;
+ private static final int KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 1;
+ private static final int KM_SECURITY_LEVEL_STRONG_BOX = 2;
+
+ private int attestationVersion;
+ private SecurityLevel attestationSecurityLevel;
+ private int keymasterVersion;
+ private SecurityLevel keymasterSecurityLevel;
+ private byte[] attestationChallenge;
+ private byte[] uniqueId;
+
+ private Map<Integer, ASN1Primitive> softwareEnforcedAuthorizations;
+ private Map<Integer, ASN1Primitive> teeEnforcedAuthorizations;
+
+ public int getAttestationVersion() {
+ return attestationVersion;
+ }
+
+ public SecurityLevel getAttestationSecurityLevel() {
+ return attestationSecurityLevel;
+ }
+
+ public int getKeymasterVersion() {
+ return keymasterVersion;
+ }
+
+ public SecurityLevel getKeymasterSecurityLevel() {
+ return attestationSecurityLevel;
+ }
+
+ public byte[] getAttestationChallenge() {
+ return attestationChallenge;
+ }
+
+ public byte[] getUniqueId() {
+ return uniqueId;
+ }
+
+ public Set<Integer> getSoftwareEnforcedAuthorizationTags() {
+ return softwareEnforcedAuthorizations.keySet();
+ }
+
+ public Set<Integer> getTeeEnforcedAuthorizationTags() {
+ return teeEnforcedAuthorizations.keySet();
+ }
+
+ private static ASN1Primitive findAuthorizationListEntry(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ return authorizationMap.getOrDefault(tag, null);
+ }
+
+ public Optional<Integer> getSoftwareAuthorizationInteger(int tag) {
+ ASN1Primitive entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
+ return Optional.ofNullable(entry).map(ParsedAttestationRecord::getIntegerFromAsn1);
+ }
+
+ public Optional<Integer> getTeeAuthorizationInteger(int tag) {
+ ASN1Primitive entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
+ return Optional.ofNullable(entry).map(ParsedAttestationRecord::getIntegerFromAsn1);
+ }
+ public boolean getSoftwareAuthorizationBoolean(int tag) {
+ ASN1Primitive entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
+ return entry != null;
+ }
+
+ public boolean getTeeAuthorizationBoolean(int tag) {
+ ASN1Primitive entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
+ return entry != null;
+ }
+
+ public Optional<byte[]> getSoftwareAuthorizationByteString(int tag) {
+ ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
+ return Optional.ofNullable(entry).map(ASN1OctetString::getOctets);
+ }
+
+ public Optional<byte[]> getTeeAuthorizationByteString(int tag) {
+ ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
+ return Optional.ofNullable(entry).map(ASN1OctetString::getOctets);
+ }
+
+ private static boolean getBooleanFromAsn1(ASN1Encodable asn1Value) {
+ if (asn1Value instanceof ASN1Boolean) {
+ return ((ASN1Boolean) asn1Value).isTrue();
+ } else {
+ throw new RuntimeException(
+ "Boolean value expected; found " + asn1Value.getClass().getName() + " instead.");
+ }
+ }
+
+ private static int getIntegerFromAsn1(ASN1Encodable asn1Value) {
+ if (asn1Value instanceof ASN1Integer) {
+ return ((ASN1Integer) asn1Value).getValue().intValue();
+ } else if (asn1Value instanceof ASN1Enumerated) {
+ return ((ASN1Enumerated) asn1Value).getValue().intValue();
+ } else {
+ throw new IllegalArgumentException(
+ "Integer value expected; found " + asn1Value.getClass().getName() + " instead.");
+ }
+ }
+
+ private static Map<Integer, ASN1Primitive> getAuthorizationMap(
+ ASN1Encodable[] authorizationList) {
+ Map<Integer, ASN1Primitive> authorizationMap = new HashMap<>();
+ for (ASN1Encodable entry : authorizationList) {
+ ASN1TaggedObject taggedEntry = (ASN1TaggedObject) entry;
+ authorizationMap.put(taggedEntry.getTagNo(), taggedEntry.getObject());
+ }
+ return authorizationMap;
+ }
+
+ public ParsedAttestationRecord(X509Certificate cert) throws IOException {
+ byte[] attestationExtensionBytes = cert.getExtensionValue(KEY_DESCRIPTION_OID);
+ if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) {
+ throw new IllegalArgumentException("Couldn't find keystore attestation extension.");
+ }
+
+ ASN1Sequence seq;
+ try (ASN1InputStream asn1InputStream = new ASN1InputStream(attestationExtensionBytes)) {
+ // The extension contains one object, a sequence, in the
+ // Distinguished Encoding Rules (DER)-encoded form. Get the DER
+ // bytes.
+ byte[] derSequenceBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
+ // Decode the bytes as an ASN1 sequence object.
+ try (ASN1InputStream seqInputStream = new ASN1InputStream(derSequenceBytes)) {
+ seq = (ASN1Sequence) seqInputStream.readObject();
+ }
+ }
+
+ this.attestationVersion = getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_VERSION_INDEX));
+ this.attestationSecurityLevel =
+ securityLevelToEnum(getIntegerFromAsn1(
+ seq.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX)));
+ this.keymasterVersion = getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_VERSION_INDEX));
+ this.keymasterSecurityLevel = securityLevelToEnum(
+ getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX)));
+ this.attestationChallenge =
+ ((ASN1OctetString) seq.getObjectAt(ATTESTATION_CHALLENGE_INDEX)).getOctets();
+ this.uniqueId = ((ASN1OctetString) seq.getObjectAt(UNIQUE_ID_INDEX)).getOctets();
+
+ this.softwareEnforcedAuthorizations = getAuthorizationMap(
+ ((ASN1Sequence) seq.getObjectAt(SW_ENFORCED_INDEX)).toArray());
+
+ this.teeEnforcedAuthorizations = getAuthorizationMap(
+ ((ASN1Sequence) seq.getObjectAt(TEE_ENFORCED_INDEX)).toArray());
+ }
+
+ private static SecurityLevel securityLevelToEnum(int securityLevel) {
+ switch (securityLevel) {
+ case KM_SECURITY_LEVEL_SOFTWARE:
+ return SecurityLevel.SOFTWARE;
+ case KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT:
+ return SecurityLevel.TRUSTED_ENVIRONMENT;
+ case KM_SECURITY_LEVEL_STRONG_BOX:
+ return SecurityLevel.STRONG_BOX;
+ default:
+ throw new IllegalArgumentException("Invalid security level.");
+ }
+ }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java b/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
index d903fac..48cc405 100644
--- a/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
@@ -106,11 +107,17 @@
static Collection<X509Certificate> createCredential(IdentityCredentialStore store,
String credentialName) throws IdentityCredentialException {
+ return createCredentialWithChallenge(store, credentialName, "SomeChallenge".getBytes());
+ }
+
+ static Collection<X509Certificate> createCredentialWithChallenge(IdentityCredentialStore store,
+ String credentialName,
+ byte[] challenge) throws IdentityCredentialException {
WritableIdentityCredential wc = null;
wc = store.createCredential(credentialName, "org.iso.18013-5.2019.mdl");
Collection<X509Certificate> certificateChain =
- wc.getCredentialKeyCertificateChain("SomeChallenge".getBytes());
+ wc.getCredentialKeyCertificateChain(challenge);
// TODO: inspect cert-chain
// Profile 0 (no authentication)
@@ -348,6 +355,9 @@
public void alreadyPersonalized() throws IdentityCredentialException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
store.deleteCredentialByName("test");
createCredential(store, "test");
@@ -365,6 +375,9 @@
public void nonExistent() throws IdentityCredentialException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
store.deleteCredentialByName("test");
IdentityCredential credential = store.getCredentialByName("test",
@@ -376,6 +389,10 @@
public void defaultStoreSupportsAnyDocumentType() throws IdentityCredentialException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
+
String[] supportedDocTypes = store.getSupportedDocTypes();
assertEquals(0, supportedDocTypes.length);
}
@@ -385,6 +402,9 @@
throws IdentityCredentialException, CborException, CertificateEncodingException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
store.deleteCredentialByName("test");
assertNull(store.deleteCredentialByName("test"));
@@ -420,6 +440,9 @@
public void testProvisionAndRetrieve() throws IdentityCredentialException, CborException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
store.deleteCredentialByName("test");
Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -508,6 +531,9 @@
InvalidKeyException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
// This checks we can do multiple getEntries() calls
@@ -569,6 +595,9 @@
public void testProvisionAndRetrieveWithFiltering() throws IdentityCredentialException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
store.deleteCredentialByName("test");
Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -620,6 +649,9 @@
public void testProvisionAndRetrieveElementWithNoACP() throws IdentityCredentialException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
store.deleteCredentialByName("test");
Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -659,6 +691,9 @@
public void testProvisionAndRetrieveWithEntryNotInRequest() throws IdentityCredentialException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
store.deleteCredentialByName("test");
Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -713,6 +748,9 @@
public void nonExistentEntries() throws IdentityCredentialException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
store.deleteCredentialByName("test");
Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -756,6 +794,9 @@
public void multipleNamespaces() throws IdentityCredentialException, CborException {
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
store.deleteCredentialByName("test");
Collection<X509Certificate> certChain = createCredentialMultipleNamespaces(
diff --git a/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java b/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
index 88abce4..1abc0ae 100644
--- a/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.security.keystore.KeyGenParameterSpec;
@@ -155,6 +156,10 @@
// Provision the credential.
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
+
String credentialName = "readerAuthTestCredential";
store.deleteCredentialByName(credentialName);
WritableIdentityCredential wc = store.createCredential(credentialName,
diff --git a/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java b/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java
index 244c636..295fdaf 100644
--- a/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java
@@ -31,11 +31,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.os.SystemClock;
import android.util.Log;
-import android.test.AndroidTestCase;
import androidx.test.InstrumentationRegistry;
@@ -92,7 +92,7 @@
import android.security.keystore.UserNotAuthenticatedException;
-public class UserAuthTest extends AndroidTestCase {
+public class UserAuthTest {
private static final String TAG = "UserAuthTest";
private class DeviceLockSession extends ActivityManagerTestBase implements AutoCloseable {
@@ -100,14 +100,14 @@
private LockScreenSession mLockCredential;
public DeviceLockSession() throws Exception {
- setUp();
mLockCredential = new LockScreenSession();
mLockCredential.setLockCredential();
}
public void performDeviceLock() {
mLockCredential.sleepDevice();
- KeyguardManager keyguardManager = (KeyguardManager)getContext().
+ Context appContext = InstrumentationRegistry.getTargetContext();
+ KeyguardManager keyguardManager = (KeyguardManager)appContext.
getSystemService(Context.KEYGUARD_SERVICE);
for (int i = 0; i < 25 && !keyguardManager.isDeviceLocked(); i++) {
SystemClock.sleep(200);
@@ -123,7 +123,6 @@
@Override
public void close() throws Exception {
mLockCredential.close();
- tearDown();
}
}
@@ -170,14 +169,18 @@
}
}
+ @Test
public void testUserAuth() throws Exception {
String alias = "authbound";
try (DeviceLockSession dl = new DeviceLockSession()) {
- KeyguardManager keyguardManager = (KeyguardManager)getContext()
+ Context appContext = InstrumentationRegistry.getTargetContext();
+ KeyguardManager keyguardManager = (KeyguardManager)appContext
.getSystemService(Context.KEYGUARD_SERVICE);
doTestUserAuth(dl, keyguardManager);
+ } catch (org.junit.AssumptionViolatedException e) {
+ /* do nothing */
}
}
@@ -196,6 +199,10 @@
// Provision the credential.
Context appContext = InstrumentationRegistry.getTargetContext();
IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (Util.isHalOptional()) {
+ assumeTrue("IC HAL not found on device", store != null);
+ }
+
store.deleteCredentialByName("test");
WritableIdentityCredential wc = store.createCredential("test",
"org.iso.18013-5.2019.mdl");
@@ -318,7 +325,7 @@
dl.performDeviceLock();
assertTrue(keyguardManager.isDeviceLocked());
dl.performDeviceUnlock();
- assertFalse(keyguardManager.isDeviceLocked());
+ assertTrue(!keyguardManager.isDeviceLocked());
credential = store.getCredentialByName("test",
CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
diff --git a/tests/tests/identity/src/android/security/identity/cts/Util.java b/tests/tests/identity/src/android/security/identity/cts/Util.java
index 52649f1..b950b89 100644
--- a/tests/tests/identity/src/android/security/identity/cts/Util.java
+++ b/tests/tests/identity/src/android/security/identity/cts/Util.java
@@ -18,8 +18,9 @@
import android.security.identity.ResultData;
-import android.util.Log;
+import android.os.SystemProperties;
import android.security.keystore.KeyProperties;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -1224,4 +1225,24 @@
throw new RuntimeException("Error generating ephemeral key-pair", e);
}
}
+
+ // Returns true if, and only if, the Identity Credential HAL (and credstore) is optional for
+ // the device under test.
+ static boolean isHalOptional() {
+ int firstApiLevel = SystemProperties.getInt("ro.product.first_api_level", 0);
+ if (firstApiLevel == 0) {
+ // ro.product.first_api_level is not set and per
+ //
+ // https://source.android.com/compatibility/cts/setup
+ //
+ // this is optional to set. In this case we simply just require the HAL.
+ return false;
+ }
+ if (firstApiLevel >= 30) {
+ // Device launched with R or later, Identity Credential HAL is not optional.
+ return false;
+ }
+ // Device launched before R, Identity Credential HAL is optional.
+ return true;
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
index 1493430..6353fed 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
@@ -35,6 +35,7 @@
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Provider;
+import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Provider.Service;
@@ -709,6 +710,64 @@
NOW_PLUS_10_YEARS);
}
+ public void testGenerate_EC_Different_Keys() throws Exception {
+ testGenerate_EC_Different_KeysHelper(false /* useStrongbox */);
+ if (TestUtils.hasStrongBox(getContext())) {
+ testGenerate_EC_Different_KeysHelper(true /* useStrongbox */);
+ }
+ }
+
+ private void testGenerate_EC_Different_KeysHelper(boolean useStrongbox) throws Exception {
+ KeyPairGenerator generator = getEcGenerator();
+ generator.initialize(new KeyGenParameterSpec.Builder(
+ TEST_ALIAS_1,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setIsStrongBoxBacked(useStrongbox)
+ .build());
+ KeyPair keyPair1 = generator.generateKeyPair();
+ PublicKey pub1 = keyPair1.getPublic();
+
+ generator.initialize(new KeyGenParameterSpec.Builder(
+ TEST_ALIAS_2,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setIsStrongBoxBacked(useStrongbox)
+ .build());
+ KeyPair keyPair2 = generator.generateKeyPair();
+ PublicKey pub2 = keyPair2.getPublic();
+ if(Arrays.equals(pub1.getEncoded(), pub2.getEncoded())) {
+ fail("The same EC key pair was generated twice");
+ }
+ }
+
+ public void testGenerate_RSA_Different_Keys() throws Exception {
+ testGenerate_RSA_Different_KeysHelper(false /* useStrongbox */);
+ if (TestUtils.hasStrongBox(getContext())) {
+ testGenerate_RSA_Different_KeysHelper(true /* useStrongbox */);
+ }
+ }
+
+ private void testGenerate_RSA_Different_KeysHelper(boolean useStrongbox) throws Exception {
+ KeyPairGenerator generator = getRsaGenerator();
+ generator.initialize(new KeyGenParameterSpec.Builder(
+ TEST_ALIAS_1,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setIsStrongBoxBacked(useStrongbox)
+ .build());
+ KeyPair keyPair1 = generator.generateKeyPair();
+ PublicKey pub1 = keyPair1.getPublic();
+
+ generator.initialize(new KeyGenParameterSpec.Builder(
+ TEST_ALIAS_2,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setIsStrongBoxBacked(useStrongbox)
+ .build());
+ KeyPair keyPair2 = generator.generateKeyPair();
+ PublicKey pub2 = keyPair2.getPublic();
+ if(Arrays.equals(pub1.getEncoded(), pub2.getEncoded())) {
+ fail("The same RSA key pair was generated twice");
+ }
+ }
+
public void testGenerate_EC_ModernSpec_Defaults() throws Exception {
testGenerate_EC_ModernSpec_DefaultsHelper(false /* useStrongbox */);
if (TestUtils.hasStrongBox(getContext())) {
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index 89ee843..27dd4ef 100644
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -86,7 +86,7 @@
/**
* Helper method to add a test provider with given name.
*/
- private void addTestProvider(final String providerName) {
+ private void addTestProvider(final String providerName) throws Exception {
mManager.addTestProvider(providerName, true, //requiresNetwork,
false, // requiresSatellite,
true, // requiresCell,
@@ -97,6 +97,7 @@
Criteria.POWER_MEDIUM, // powerRequirement
Criteria.ACCURACY_FINE); // accuracy
mManager.setTestProviderEnabled(providerName, true);
+ Thread.sleep(500);
}
@Override
@@ -146,7 +147,7 @@
}
}
- public void testGetProviders() {
+ public void testGetProviders() throws Exception {
List<String> providers = mManager.getAllProviders();
assertTrue(providers.size() >= 2);
assertTrue(hasTestProvider(providers));
@@ -165,6 +166,8 @@
int oldSizeTrueProviders = providers.size();
mManager.setTestProviderEnabled(TEST_MOCK_PROVIDER_NAME, false);
+ Thread.sleep(500);
+
providers = mManager.getProviders(true);
assertEquals(oldSizeTrueProviders - 1, providers.size());
assertFalse(hasTestProvider(providers));
@@ -272,7 +275,7 @@
* throws an {@link java.lang.IllegalArgumentException} if there is no such test provider,
* so we have to add it before we clear it.
*/
- private void forceRemoveTestProvider(String provider) {
+ private void forceRemoveTestProvider(String provider) throws Exception {
addTestProvider(provider);
mManager.removeTestProvider(provider);
}
@@ -353,7 +356,7 @@
* known way to determine if a given provider is a test provider.
* @throws InterruptedException
*/
- public void testReplaceRealProvidersWithMocks() throws InterruptedException {
+ public void testReplaceRealProvidersWithMocks() throws Exception {
for (String providerName : mManager.getAllProviders()) {
if (!providerName.equals(TEST_MOCK_PROVIDER_NAME) &&
!providerName.equals(LocationManager.PASSIVE_PROVIDER)) {
@@ -530,7 +533,7 @@
}
}
- public void testLocationUpdatesWithCriteriaAndPendingIntent() throws InterruptedException {
+ public void testLocationUpdatesWithCriteriaAndPendingIntent() throws Exception {
double latitude1 = 10;
double longitude1 = 20;
double latitude2 = 30;
@@ -586,7 +589,7 @@
unmockFusedLocation();
}
- public void testSingleUpdateWithCriteriaAndPendingIntent() throws InterruptedException {
+ public void testSingleUpdateWithCriteriaAndPendingIntent() throws Exception {
double latitude1 = 10;
double longitude1 = 20;
double latitude2 = 30;
@@ -650,7 +653,7 @@
}
public void testLocationUpdatesWithCriteriaAndLocationListenerAndLooper()
- throws InterruptedException {
+ throws Exception {
double latitude1 = 40;
double longitude1 = 10;
double latitude2 = 20;
@@ -706,7 +709,7 @@
}
public void testSingleUpdateWithCriteriaAndLocationListenerAndLooper()
- throws InterruptedException {
+ throws Exception {
double latitude1 = 40;
double longitude1 = 10;
double latitude2 = 20;
@@ -850,7 +853,7 @@
mManager.removeNmeaListener((OnNmeaMessageListener) null);
}
- public void testIsProviderEnabled() {
+ public void testIsProviderEnabled() throws Exception {
// this test assumes enabled TEST_MOCK_PROVIDER_NAME was created in setUp.
assertNotNull(mManager.getProvider(TEST_MOCK_PROVIDER_NAME));
assertTrue(mManager.isProviderEnabled(TEST_MOCK_PROVIDER_NAME));
@@ -867,7 +870,7 @@
mManager.setTestProviderEnabled(TEST_MOCK_PROVIDER_NAME, true);
try {
- Thread.sleep(100);
+ Thread.sleep(500);
} catch (Exception e) {
Log.e(TAG, "fail in testIsProviderEnabled");
}
@@ -1183,7 +1186,7 @@
return criteria;
}
- private void mockFusedLocation() {
+ private void mockFusedLocation() throws Exception {
addTestProvider(FUSED_PROVIDER_NAME);
}
diff --git a/tests/tests/media/OWNERS b/tests/tests/media/OWNERS
index 4b97bcb..6614de9 100644
--- a/tests/tests/media/OWNERS
+++ b/tests/tests/media/OWNERS
@@ -3,6 +3,7 @@
chz@google.com
dwkang@google.com
elaurent@google.com
+essick@google.com
etalvala@google.com
gkasten@google.com
hdmoon@google.com
diff --git a/tests/tests/media/res/raw/a_4_haptic.ogg b/tests/tests/media/res/raw/a_4_haptic.ogg
index e8287a8..3c94c8d 100644
--- a/tests/tests/media/res/raw/a_4_haptic.ogg
+++ b/tests/tests/media/res/raw/a_4_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/b_5_haptic.ogg b/tests/tests/media/res/raw/b_5_haptic.ogg
index cb4df5a..90ab939 100644
--- a/tests/tests/media/res/raw/b_5_haptic.ogg
+++ b/tests/tests/media/res/raw/b_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/c_sharp_5_haptic.ogg b/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
index 7950552..57c46ea 100644
--- a/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
+++ b/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/e_5_haptic.ogg b/tests/tests/media/res/raw/e_5_haptic.ogg
index 7c7f4cd..98289dc 100644
--- a/tests/tests/media/res/raw/e_5_haptic.ogg
+++ b/tests/tests/media/res/raw/e_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/g_sharp_5_haptic.ogg b/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
index ecf9439..6fb7388 100644
--- a/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
+++ b/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
index 4c234ce..7e353ea 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
@@ -422,7 +422,7 @@
// As a result, read() should fail after at most the total buffer size read.
// Even if the projection is stopped, the policy unregisteration is async,
// so double that to be on the conservative side.
- final int MAX_READ_SIZE = 2 * nativeBufferSize;
+ final int MAX_READ_SIZE = 8 * nativeBufferSize;
int readSize = 0;
ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
int status;
diff --git a/tests/tests/media/src/android/media/cts/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
index 5f1d322..2384704 100644
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/src/android/media/cts/CodecState.java
@@ -19,6 +19,7 @@
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
+import android.os.Handler;
import android.util.Log;
import android.view.Surface;
import java.nio.ByteBuffer;
@@ -50,6 +51,7 @@
private MediaFormat mFormat;
private MediaFormat mOutputFormat;
private NonBlockingAudioTrack mAudioTrack;
+ private OnFrameRenderedListener mOnFrameRenderedListener;
/**
* Manages audio and video playback using MediaCodec and AudioTrack.
@@ -84,6 +86,11 @@
String mime = mFormat.getString(MediaFormat.KEY_MIME);
Log.d(TAG, "CodecState::CodecState " + mime);
mIsAudio = mime.startsWith("audio/");
+
+ if (mTunneled && !mIsAudio) {
+ mOnFrameRenderedListener = new OnFrameRenderedListener();
+ codec.setOnFrameRenderedListener(mOnFrameRenderedListener, new Handler());
+ }
}
public void release() {
@@ -100,6 +107,11 @@
mAvailableOutputBufferIndices = null;
mAvailableOutputBufferInfos = null;
+ if (mOnFrameRenderedListener != null) {
+ mCodec.setOnFrameRenderedListener(null, null);
+ mOnFrameRenderedListener = null;
+ }
+
mCodec.release();
mCodec = null;
@@ -218,11 +230,6 @@
Log.d(TAG, "sampleSize: " + sampleSize + " trackIndex:" + trackIndex +
" sampleTime:" + sampleTime + " sampleFlags:" + sampleFlags);
mSawInputEOS = true;
- // FIX-ME: in tunneled mode we currently use input EOS as output EOS indicator
- // we should use stream duration
- if (mTunneled && !mIsAudio) {
- mSawOutputEOS = true;
- }
return false;
}
@@ -231,9 +238,6 @@
mSampleBaseTimeUs = sampleTime;
}
sampleTime -= mSampleBaseTimeUs;
- // FIX-ME: in tunneled mode we currently use input buffer time
- // as video presentation time. This is not accurate and should be fixed
- mPresentationTimeUs = sampleTime;
}
if ((sampleFlags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
@@ -255,11 +259,6 @@
Log.d(TAG, "saw input EOS on track " + mTrackIndex);
mSawInputEOS = true;
- // FIX-ME: in tunneled mode we currently use input EOS as output EOS indicator
- // we should use stream duration
- if (mTunneled && !mIsAudio) {
- mSawOutputEOS = true;
- }
mCodec.queueInputBuffer(
index, 0 /* offset */, 0 /* sampleSize */,
@@ -375,6 +374,23 @@
}
}
+ /** Callback called by the renderer in tunneling mode. */
+ private class OnFrameRenderedListener implements MediaCodec.OnFrameRenderedListener {
+ private static final long TUNNELING_EOS_PRESENTATION_TIME_US = Long.MAX_VALUE;
+
+ @Override
+ public void onFrameRendered(MediaCodec codec, long presentationTimeUs, long nanoTime) {
+ if (this != mOnFrameRenderedListener) {
+ return; // stale event
+ }
+ if (presentationTimeUs == TUNNELING_EOS_PRESENTATION_TIME_US) {
+ mSawOutputEOS = true;
+ } else {
+ mPresentationTimeUs = presentationTimeUs;
+ }
+ }
+ }
+
public long getAudioTimeUs() {
if (mAudioTrack == null) {
return 0;
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
index 90c6572..e0ec13a 100755
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
@@ -44,6 +44,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
/**
* Tests connecting a virtual display to the input of a MediaCodec encoder.
@@ -540,6 +541,7 @@
private void showPresentation(final int color) {
final TestPresentation[] presentation = new TestPresentation[1];
try {
+ final CountDownLatch latch = new CountDownLatch(1);
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -548,13 +550,17 @@
presentation[0] = new TestPresentation(getContext(), mDisplay, color);
if (VERBOSE) Log.d(TAG, "showing color=0x" + Integer.toHexString(color));
presentation[0].show();
+ latch.countDown();
}
});
// Give the presentation an opportunity to render. We don't have a way to
// monitor the output, so we just sleep for a bit.
- try { Thread.sleep(UI_RENDER_PAUSE_MS); }
- catch (InterruptedException ignore) {}
+ try {
+ // wait for the UI thread execution to finish
+ latch.await();
+ Thread.sleep(UI_RENDER_PAUSE_MS);
+ } catch (InterruptedException ignore) {}
} finally {
if (presentation[0] != null) {
runOnUiThread(new Runnable() {
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index f0c3d1d..cad25ed 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -15,6 +15,8 @@
*/
package android.media.cts;
+import android.app.ActivityManager;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
@@ -33,6 +35,7 @@
import static android.media.MediaFormat.MIMETYPE_VIDEO_VP9;
import android.media.MediaPlayer;
import android.os.Build;
+import android.os.SystemProperties;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import android.util.Range;
@@ -691,13 +694,16 @@
return format;
}
- private static int getActualMax(
+ private int getActualMax(
boolean isEncoder, String name, String mime, CodecCapabilities caps, int max) {
int flag = isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
MediaFormat format = createMinFormat(mime, caps);
Log.d(TAG, "Test format " + format);
Vector<MediaCodec> codecs = new Vector<MediaCodec>();
MediaCodec codec = null;
+ ActivityManager am = (ActivityManager)
+ mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
for (int i = 0; i < max; ++i) {
try {
Log.d(TAG, "Create codec " + name + " #" + i);
@@ -706,6 +712,12 @@
codec.start();
codecs.add(codec);
codec = null;
+
+ am.getMemoryInfo(outInfo);
+ if (outInfo.lowMemory) {
+ Log.d(TAG, "System is in low memory condition, stopping. max: " + i);
+ break;
+ }
} catch (IllegalArgumentException e) {
fail("Got unexpected IllegalArgumentException " + e.getMessage());
} catch (IOException e) {
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 6a10e40..1b4d4e2 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -1255,49 +1255,6 @@
mMediaPlayer.stop();
}
- public void testMkvWithoutCueSeek() throws Exception {
- if (!checkLoadResource(
- R.raw.video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz_withoutcues)) {
- return; // skip
- }
-
- mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
- @Override
- public void onSeekComplete(MediaPlayer mp) {
- mOnSeekCompleteCalled.signal();
- }
- });
- mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
- mMediaPlayer.prepare();
- mOnSeekCompleteCalled.reset();
- mMediaPlayer.start();
-
- final int seekPosMs = 3000;
- final int syncTime2Ms = 5960;
-
- int cp = runSeekMode(MediaPlayer.SEEK_CLOSEST, seekPosMs);
- Log.d(LOG_TAG, "runSeekMode SEEK_CLOSEST cp: " + cp);
- assertTrue("MediaPlayer seek fail with SEEK_CLOSEST mode.",
- cp > seekPosMs && cp < syncTime2Ms);
-
- cp = runSeekMode(MediaPlayer.SEEK_PREVIOUS_SYNC, seekPosMs);
- Log.d(LOG_TAG, "runSeekMode SEEK_PREVIOUS_SYNC cp: " + cp);
- assertTrue("MediaPlayer seek fail with SEEK_PREVIOUS_SYNC mode.",
- cp >= syncTime2Ms);
-
- cp = runSeekMode(MediaPlayer.SEEK_NEXT_SYNC, seekPosMs);
- Log.d(LOG_TAG, "runSeekMode SEEK_NEXT_SYNC cp: " + cp);
- assertTrue("MediaPlayer seek fail with SEEK_NEXT_SYNC mode.",
- cp >= syncTime2Ms);
-
- cp = runSeekMode(MediaPlayer.SEEK_CLOSEST_SYNC, seekPosMs);
- Log.d(LOG_TAG, "runSeekMode SEEK_CLOSEST_SYNC cp: " + cp);
- assertTrue("MediaPlayer seek fail with SEEK_CLOSEST_SYNC mode.",
- cp >= syncTime2Ms);
-
- mMediaPlayer.stop();
- }
-
public void testSeekModes() throws Exception {
// This clip has 2 I frames at 66687us and 4299687us.
if (!checkLoadResource(
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolTest.java b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
index 2c56acd..e9b8fd1 100644
--- a/tests/tests/media/src/android/media/cts/SoundPoolTest.java
+++ b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
@@ -287,8 +287,8 @@
}
// wait for all sounds to load,
- // it usually takes about 33 seconds and 50 seconds is used to have some headroom.
- final long LOAD_TIMEOUT_IN_MS = 50000;
+ // it usually takes about 33 seconds and 100 seconds is used to have some headroom.
+ final long LOAD_TIMEOUT_IN_MS = 100000;
final long startTime = System.currentTimeMillis();
synchronized(done) {
while (loaded[0] != soundIds.length) {
diff --git a/tests/tests/net/Android.bp b/tests/tests/net/Android.bp
index b00455d..624d149 100644
--- a/tests/tests/net/Android.bp
+++ b/tests/tests/net/Android.bp
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-android_test {
- name: "CtsNetTestCases",
+java_defaults {
+ name: "CtsNetTestCasesDefaults",
defaults: ["cts_defaults"],
// Include both the 32 and 64 bit versions
@@ -32,7 +32,6 @@
"libnativehelper_compat_libc++",
],
- // include CtsTestServer as a temporary hack to free net.cts from cts.stub.
srcs: [
"src/**/*.java",
"src/**/*.kt",
@@ -54,13 +53,33 @@
// uncomment when b/13249961 is fixed
// sdk_version: "current",
platform_apis: true,
+}
- // Tag this module as a cts test artifact
+// Networking CTS tests for development and release. These tests always target the platform SDK
+// version, and are subject to all the restrictions appropriate to that version. Before SDK
+// finalization, these tests have a min_sdk_version of 10000, and cannot be installed on release
+// devices.
+android_test {
+ name: "CtsNetTestCases",
+ defaults: ["CtsNetTestCasesDefaults"],
test_suites: [
"cts",
"vts",
"general-tests",
+ ],
+ test_config_template: "AndroidTestTemplate.xml",
+}
+
+// Networking CTS tests that have a min_sdk_version of the latest released SDK. These tests can
+// be installed on release devices at any point in the release cycle and are useful for qualifying
+// mainline modules on release devices.
+android_test {
+ name: "CtsNetTestCasesLatestSdk",
+ defaults: ["CtsNetTestCasesDefaults"],
+ min_sdk_version: "29",
+ target_sdk_version: "29",
+ test_suites: [
"mts",
],
-
+ test_config_template: "AndroidTestTemplate.xml",
}
diff --git a/tests/tests/net/AndroidTest.xml b/tests/tests/net/AndroidTestTemplate.xml
similarity index 92%
rename from tests/tests/net/AndroidTest.xml
rename to tests/tests/net/AndroidTestTemplate.xml
index 3ff019e..1f75da1 100644
--- a/tests/tests/net/AndroidTest.xml
+++ b/tests/tests/net/AndroidTestTemplate.xml
@@ -12,7 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<configuration description="Config for CTS Net test cases">
+<configuration description="Test config for {MODULE}">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
@@ -22,7 +22,7 @@
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsNetTestCases.apk" />
+ <option name="test-file-name" value="{MODULE}.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.cts" />
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
new file mode 100644
index 0000000..0a80047
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 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.net.cts;
+
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.NetworkRequest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class ConnectivityDiagnosticsManagerTest {
+ private static final Executor INLINE_EXECUTOR = x -> x.run();
+ private static final NetworkRequest DEFAULT_REQUEST = new NetworkRequest.Builder().build();
+
+ private Context mContext;
+ private ConnectivityDiagnosticsManager mCdm;
+ private ConnectivityDiagnosticsCallback mCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mCdm = mContext.getSystemService(ConnectivityDiagnosticsManager.class);
+
+ mCallback = new ConnectivityDiagnosticsCallback() {};
+ }
+
+ @Test
+ public void testRegisterConnectivityDiagnosticsCallback() {
+ mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+ }
+
+ @Test
+ public void testRegisterDuplicateConnectivityDiagnosticsCallback() {
+ mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+
+ try {
+ mCdm.registerConnectivityDiagnosticsCallback(
+ DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+ fail("Registering the same callback twice should throw an IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testUnregisterConnectivityDiagnosticsCallback() {
+ mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+ mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+ }
+
+ @Test
+ public void testUnregisterUnknownConnectivityDiagnosticsCallback() {
+ // Expected to silently ignore the unregister() call
+ mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+ }
+}
diff --git a/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java
index 628571c..c557338 100644
--- a/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -272,6 +272,14 @@
+ " needs Location enabled.");
}
+ mWifiP2pManager =
+ (WifiP2pManager) getContext().getSystemService(Context.WIFI_P2P_SERVICE);
+ mWifiP2pChannel = mWifiP2pManager.initialize(
+ getContext(), getContext().getMainLooper(), null);
+
+ assertNotNull(mWifiP2pManager);
+ assertNotNull(mWifiP2pChannel);
+
long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
while (!mWifiManager.isWifiEnabled() && System.currentTimeMillis() < timeout) {
try {
@@ -288,14 +296,6 @@
assertEquals(WifiManager.WIFI_STATE_ENABLED, mMySync.expectedWifiState);
assertEquals(WifiP2pManager.WIFI_P2P_STATE_ENABLED, mMySync.expectedP2pState);
- mWifiP2pManager =
- (WifiP2pManager) getContext().getSystemService(Context.WIFI_P2P_SERVICE);
- mWifiP2pChannel = mWifiP2pManager.initialize(
- getContext(), getContext().getMainLooper(), null);
-
- assertNotNull(mWifiP2pManager);
- assertNotNull(mWifiP2pChannel);
-
assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
// wait for changing to EnabledState
assertNotNull(mMySync.expectedNetworkInfo);
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
index b592c10..e4c4b00 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
@@ -687,6 +687,9 @@
@Override
protected void setUp() throws Exception {
super.setUp();
+ if(!hasWifi()) {
+ return;
+ }
mWifiManager = (WifiManager) mContext
.getSystemService(Context.WIFI_SERVICE);
assertNotNull(mWifiManager);
@@ -778,6 +781,9 @@
}
public void testEnterpriseConfigDoesNotPrintPassword() {
+ if(!hasWifi()) {
+ return;
+ }
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
final String identity = "IdentityIsOkayToBeDisplayedHere";
final String password = "PasswordIsNotOkayToBeDisplayedHere";
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.mk b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
index e9ea001..1b6e7b3 100644
--- a/tests/tests/neuralnetworks/tflite_delegate/Android.mk
+++ b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
@@ -20,7 +20,11 @@
LOCAL_SRC_FILES := \
tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc \
tensorflow/lite/kernels/test_util.cc \
- tensorflow/core/platform/default/logging.cc
+ tensorflow/core/platform/default/logging.cc \
+ tensorflow/lite/kernels/acceleration_test_util.cc \
+ tensorflow/lite/kernels/acceleration_test_util_internal.cc \
+ tensorflow/lite/delegates/nnapi/acceleration_test_list.cc \
+ tensorflow/lite/delegates/nnapi/acceleration_test_util.cc
LOCAL_CPP_EXTENSION := .cc
LOCAL_C_INCLUDES += external/flatbuffers/include
@@ -38,6 +42,7 @@
LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
+LOCAL_HEADER_LIBRARIES := libeigen gemmlowp_headers
LOCAL_SDK_VERSION := current
LOCAL_NDK_STL_VARIANT := c++_static
include $(BUILD_STATIC_LIBRARY)
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
index 6e87253..b999cfe 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
@@ -39,6 +39,7 @@
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
import android.util.ArraySet;
import android.util.Log;
@@ -175,6 +176,7 @@
// trigger a change in the bound service's condition
((LegacyConditionProviderService) LegacyConditionProviderService.getInstance())
.toggleDND(false);
+ sleep(500);
// verify that the unbound service maintains it's DND vote
assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
@@ -293,8 +295,13 @@
}
private void addRule(ComponentName cn, int filter, boolean enabled) {
+ final Uri conditionId = new Uri.Builder()
+ .scheme(Condition.SCHEME)
+ .authority(ZenModeConfig.SYSTEM_AUTHORITY)
+ .appendPath(cn.toString())
+ .build();
String id = mNm.addAutomaticZenRule(new AutomaticZenRule("name",
- cn, Uri.EMPTY, filter, enabled));
+ cn, conditionId, filter, enabled));
Log.d(TAG, "Created rule with id " + id);
ids.add(id);
}
diff --git a/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java b/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
index 6a3290f..e61b266 100644
--- a/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
@@ -50,6 +50,8 @@
fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
} catch (SecurityException e) {
// Expected exception
+ } catch (IllegalArgumentException e) {
+ // TvProvider is not visable for instant app
}
}
@@ -60,6 +62,8 @@
fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
} catch (SecurityException e) {
// Expected exception
+ } catch (IllegalArgumentException e) {
+ // TvProvider is not visable for instant app
}
}
@@ -69,6 +73,8 @@
fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
} catch (SecurityException e) {
// Expected exception
+ } catch (IllegalArgumentException e) {
+ // TvProvider is not visable for instant app
}
}
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 0f1eba7..613b3df 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -2484,7 +2484,7 @@
<p>Not for use by third-party applications.
@hide
-->
- <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"
+ <permission android:name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE"
android:protectionLevel="signature|telephony" />
<!-- Allows applications like settings to suggest the user's manually chosen time / time zone.
@@ -3569,6 +3569,13 @@
<permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
android:protectionLevel="signature" />
+ <!-- Allows an application to control the lights on the device.
+ @hide
+ @SystemApi
+ @TestApi -->
+ <permission android:name="android.permission.CONTROL_DEVICE_LIGHTS"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to control the color saturation of the display.
@hide
@SystemApi -->
@@ -4629,15 +4636,15 @@
<!-- Allows an app to log compat change usage.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.LOG_COMPAT_CHANGE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|privileged" />
<!-- Allows an app to read compat change config.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|privileged" />
<!-- Allows an app to override compat change config.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|privileged" />
<!-- Allows input events to be monitored. Very dangerous! @hide -->
<permission android:name="android.permission.MONITOR_INPUT"
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
index be9d1d2..b8391d7 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
@@ -50,7 +50,7 @@
public void testGetContentContactDir() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.setData(ContactsContract.Contacts.CONTENT_URI);
+ intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
assertCanBeHandled(intent);
}
}
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index 45220d9..4dfec99 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -17,7 +17,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/security/src/android/security/cts/ServicePermissionsTest.java b/tests/tests/security/src/android/security/cts/ServicePermissionsTest.java
index 60e27e6..bccbe59 100644
--- a/tests/tests/security/src/android/security/cts/ServicePermissionsTest.java
+++ b/tests/tests/security/src/android/security/cts/ServicePermissionsTest.java
@@ -107,6 +107,7 @@
} catch (SecurityException e) {
String msg = e.getMessage();
if ((msg == null) || msg.contains("android.permission.DUMP")) {
+ Log.d(TAG, "Service " + service + " correctly checked permission");
// Service correctly checked for DUMP permission, yay
} else {
// Service is throwing about something else; they're
@@ -115,7 +116,9 @@
continue;
}
} catch (TransactionTooLargeException | DeadObjectException e) {
- // SELinux likely prevented the dump - assume safe
+ // SELinux likely prevented the dump - assume safe, but log anywasy
+ // (as the exception might happens in some devices but not on others)
+ Log.w(TAG, "Service " + service + " threw exception: " + e);
continue;
} finally {
out.close();
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index d1daa5a..fdf5676 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -1240,8 +1240,10 @@
Pattern.compile("android\\.hardware\\.drm@\\d+?\\.\\d+?-service"),
Pattern.compile("android\\.hardware\\.drm@\\d+?\\.\\d+?-service\\.clearkey"),
Pattern.compile("android\\.hardware\\.drm@\\d+?\\.\\d+?-service\\.widevine"),
+ Pattern.compile("omx@\\d+?\\.\\d+?-service"), // name:omx@1.0-service
Pattern.compile("android\\.process\\.media"),
Pattern.compile("mediadrmserver"),
+ Pattern.compile("mediaextractor"),
Pattern.compile("media\\.extractor"),
Pattern.compile("media\\.metrics"),
Pattern.compile("mediaserver"),
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index 116cc12..a6fe2bf 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -283,8 +283,8 @@
mConnection.setConnectionProperties(Connection.PROPERTY_IS_RTT);
assertCallProperties(mCall, Call.Details.PROPERTY_RTT);
- mConnection.setConnectionProperties(Connection.PROPERTY_ASSISTED_DIALING_USED);
- assertCallProperties(mCall, Call.Details.PROPERTY_ASSISTED_DIALING_USED);
+ mConnection.setConnectionProperties(Connection.PROPERTY_ASSISTED_DIALING);
+ assertCallProperties(mCall, Call.Details.PROPERTY_ASSISTED_DIALING);
mConnection.setConnectionProperties(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
assertCallProperties(mCall, Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
@@ -870,16 +870,26 @@
}
/**
- * Tests whether the CallLogManager logs the features of a call(HD call and Wifi call)
+ * Tests whether the CallLogManager logs the features of a call(HD call, Wifi call, VoLTE)
* correctly.
*/
- public void testLogHdAndWifi() throws Exception {
+ public void testLogFeatures() throws Exception {
if (!mShouldTestTelecom) {
return;
}
// Register content observer on call log and get latch
CountDownLatch callLogEntryLatch = getCallLogEntryLatch();
+
+ Bundle testBundle = new Bundle();
+ testBundle.putInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE,
+ TelephonyManager.NETWORK_TYPE_LTE);
+ mConnection.putExtras(testBundle);
+ // Wait for the 2nd invocation; setExtras is called in the setup method.
+ mOnExtrasChangedCounter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+ Bundle extra = mCall.getDetails().getExtras();
+
mCall.disconnect();
// Wait on the call log latch.
@@ -887,11 +897,13 @@
// Verify the contents of the call log
Cursor callsCursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null,
- "features", null, null);
- int features = callsCursor.getColumnIndex(CallLog.Calls.FEATURES);
+ null, null, "_id DESC");
+ callsCursor.moveToFirst();
+ int features = callsCursor.getInt(callsCursor.getColumnIndex("features"));
assertEquals(CallLog.Calls.FEATURES_HD_CALL,
features & CallLog.Calls.FEATURES_HD_CALL);
assertEquals(CallLog.Calls.FEATURES_WIFI, features & CallLog.Calls.FEATURES_WIFI);
+ assertEquals(CallLog.Calls.FEATURES_VOLTE, features & CallLog.Calls.FEATURES_VOLTE);
}
/**
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
index 771f09b..cb0d769 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
@@ -199,7 +199,7 @@
// Consumed internally in Telecom; no verifiable manner to see the end point of this data
// through public APIs.
mConferenceObject.setConnectionTime(0);
- mConferenceObject.setConnectionStartElapsedRealTime(0);
+ mConferenceObject.setConnectionStartElapsedRealtimeMillis(0);
Bundle extras = new Bundle();
extras.putString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE, "Test");
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
index e0bb29e..08931eb 100755
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
@@ -272,8 +272,8 @@
connection.setCallDirection(Call.Details.DIRECTION_OUTGOING);
connection.setConnectTimeMillis(1000L);
assertEquals(1000L, connection.getConnectTimeMillis());
- connection.setConnectionStartElapsedRealTime(100L);
- assertEquals(100L, connection.getConnectElapsedTimeMillis());
+ connection.setConnectionStartElapsedRealtimeMillis(100L);
+ assertEquals(100L, connection.getConnectionStartElapsedRealtimeMillis());
CtsConnectionService.addExistingConnectionToTelecom(TEST_PHONE_ACCOUNT_HANDLE, connection);
assertNumCalls(mInCallCallbacks.getService(), 2);
diff --git a/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java b/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
index ec0a280..9bdf6f7 100644
--- a/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
@@ -97,7 +97,7 @@
assertEquals(TestUtils.PACKAGE, mTelecomManager.getDefaultDialerPackage());
assertEquals(mTelecomManager.getDefaultDialerPackage(),
ShellIdentityUtils.invokeMethodWithShellPermissions(mTelecomManager,
- tm -> tm.getDefaultDialerPackage(Process.myUserHandle().getIdentifier())));
+ tm -> tm.getDefaultDialerPackage(Process.myUserHandle())));
}
/** Default dialer should be the default package handling ACTION_DIAL. */
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index 4ba93db..92cd25b 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -35,6 +35,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+
import android.app.UiAutomation;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -83,7 +84,7 @@
@After
public void tearDown() throws Exception {
try {
- setOpMode("android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
+ setOpMode("--uid android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
} catch (IOException e) {
fail();
}
@@ -166,7 +167,7 @@
PersistableBundle config;
try {
- setOpMode("android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_IGNORED);
+ setOpMode("--uid android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_IGNORED);
} catch (IOException e) {
fail();
}
@@ -175,7 +176,7 @@
assertTrue(config.isEmptyParcel());
try {
- setOpMode("android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
+ setOpMode("--uid android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
} catch (IOException e) {
fail();
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
index 4035372..4c4d93d 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
@@ -44,6 +44,7 @@
private static final SmsCbLocation TEST_LOCATION = new SmsCbLocation(TEST_PLMN, -1, -1);
private static final int TEST_SERVICE_CATEGORY = 4097;
private static final String TEST_LANGUAGE = "en";
+ private static final int TEST_DCS = 0;
private static final String TEST_BODY = "test body";
private static final int TEST_PRIORITY = 0;
private static final int TEST_ETWS_WARNING_TYPE =
@@ -61,9 +62,9 @@
public void testGetIntentForBackgroundReceivers() {
try {
SmsCbMessage message = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE,
- TEST_SERIAL, TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
- TEST_PRIORITY, TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS,
- TEST_RECEIVED_TIME, TEST_SLOT, TEST_SUB_ID);
+ TEST_SERIAL, TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_DCS,
+ TEST_BODY, TEST_PRIORITY, TEST_ETWS_INFO, null,
+ TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME, TEST_SLOT, TEST_SUB_ID);
CellBroadcastIntents.sendSmsCbReceivedBroadcast(
InstrumentationRegistry.getContext(), UserHandle.ALL, message,
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
index ab6937f..e1589b2 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
@@ -324,8 +324,9 @@
assertTrue("Invalid timestamp in CellInfo: " + info.getTimeStamp(),
info.getTimeStamp() > 0 && info.getTimeStamp() < Long.MAX_VALUE);
- assertTrue("Invalid timestamp in CellInfo: " + info.getTimestampNanos(),
- info.getTimestampNanos() > 0 && info.getTimestampNanos() < Long.MAX_VALUE);
+ long curTime = SystemClock.elapsedRealtime();
+ assertTrue("Invalid timestamp in CellInfo: " + info.getTimestampMillis(),
+ info.getTimestampMillis() > 0 && info.getTimestampMillis() <= curTime);
if (mRadioHalVersion >= RADIO_HAL_VERSION_1_2) {
// In HAL 1.2 or greater, the connection status must be reported
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/NetworkRegistrationInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/NetworkRegistrationInfoTest.java
index 427ede3..a5e75c5 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/NetworkRegistrationInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/NetworkRegistrationInfoTest.java
@@ -15,17 +15,18 @@
*/
package android.telephony.cts;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.TelephonyManager;
-import java.util.Arrays;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
import org.junit.Test;
+import java.util.Arrays;
+
public class NetworkRegistrationInfoTest {
@Test
@@ -87,6 +88,30 @@
}
@Test
+ public void testGetEmergencyServices() {
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setAvailableServices(Arrays.asList(NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY,
+ NetworkRegistrationInfo.SERVICE_TYPE_VOICE))
+ .build();
+ assertEquals(Arrays.asList(NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY,
+ NetworkRegistrationInfo.SERVICE_TYPE_VOICE), nri.getAvailableServices());
+ }
+
+ /**
+ * Basic test to ensure {@link NetworkRegistrationInfo#isSearching()} does not throw any
+ * exception.
+ */
+ @Test
+ public void testNetworkRegistrationInfoIsSearching() {
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setRegistrationState(
+ NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING)
+ .build();
+ assertTrue(nri.isSearching());
+ }
+
+
+ @Test
public void testGetDomain() {
NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
.setDomain(NetworkRegistrationInfo.DOMAIN_CS)
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
index b2c532a..5f85093 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
@@ -292,6 +292,105 @@
}
@Test
+ public void testOnAlwaysReportedSignalStrengthChanged() throws Throwable {
+ if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+ Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+ return;
+ }
+
+ TestThread t = new TestThread(new Runnable() {
+ public void run() {
+ Looper.prepare();
+
+ mListener = new PhoneStateListener() {
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ synchronized (mLock) {
+ mSignalStrength = signalStrength;
+ mLock.notify();
+ }
+ }
+ };
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+ (tm) -> tm.listen(mListener,
+ PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
+
+ Looper.loop();
+ }
+ });
+
+ assertTrue(mSignalStrength == null);
+ t.start();
+
+ synchronized (mLock) {
+ if (mSignalStrength == null) {
+ mLock.wait(WAIT_TIME);
+ }
+ }
+ t.checkException();
+ assertTrue(mSignalStrength != null);
+
+ // Call SignalStrength methods to make sure they do not throw any exceptions
+ mSignalStrength.getCdmaDbm();
+ mSignalStrength.getCdmaEcio();
+ mSignalStrength.getEvdoDbm();
+ mSignalStrength.getEvdoEcio();
+ mSignalStrength.getEvdoSnr();
+ mSignalStrength.getGsmBitErrorRate();
+ mSignalStrength.getGsmSignalStrength();
+ mSignalStrength.isGsm();
+ mSignalStrength.getLevel();
+ }
+
+ /**
+ * Validate that SecurityException should be thrown when listen
+ * with LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH without LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH
+ * permission.
+ */
+ @Test
+ public void testOnAlwaysReportedSignalStrengthChangedWithoutPermission() throws Throwable {
+ if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+ Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+ return;
+ }
+
+ TestThread t = new TestThread(new Runnable() {
+ public void run() {
+ Looper.prepare();
+
+ mListener = new PhoneStateListener() {
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ synchronized (mLock) {
+ mSignalStrength = signalStrength;
+ mLock.notify();
+ }
+ }
+ };
+ try {
+ mTelephonyManager.listen(mListener,
+ PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
+ } catch (SecurityException se) {
+ mSecurityExceptionThrown = true;
+ mLock.notify();
+ }
+ Looper.loop();
+ }
+ });
+
+ assertTrue(mSignalStrength == null);
+ t.start();
+
+ synchronized (mLock) {
+ if (!mSecurityExceptionThrown) {
+ mLock.wait(WAIT_TIME);
+ }
+ }
+ assertThat(mSecurityExceptionThrown).isTrue();
+ assertTrue(mSignalStrength == null);
+ }
+
+ @Test
public void testOnSignalStrengthsChanged() throws Throwable {
if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java
index 3077bbb..0849b64 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java
@@ -38,13 +38,13 @@
import android.telephony.TelephonyManager;
import android.util.Log;
+import org.junit.Before;
+import org.junit.Test;
+
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import org.junit.Before;
-import org.junit.Test;
-
/**
* Test SignalStrength to ensure that valid data is being reported and that invalid data is
* not reported.
@@ -81,10 +81,10 @@
SignalStrength ss = mTm.getSignalStrength();
assertNotNull("TelephonyManager.getSignalStrength() returned NULL!", ss);
- long curTime = SystemClock.elapsedRealtimeNanos();
- assertTrue("Invalid timestamp in SignalStrength: " + ss.getTimestampNanos(),
- ss.getTimestampNanos() > 0 && ss.getTimestampNanos() < curTime);
- Log.d(TAG, "Timestamp of SignalStrength: " + Long.toString(ss.getTimestampNanos()));
+ long curTime = SystemClock.elapsedRealtime();
+ assertTrue("Invalid timestamp in SignalStrength: " + ss.getTimestampMillis(),
+ ss.getTimestampMillis() > 0 && ss.getTimestampMillis() <= curTime);
+ Log.d(TAG, "Timestamp of SignalStrength: " + Long.toString(ss.getTimestampMillis()));
List<CellSignalStrength> signalStrengths = ss.getCellSignalStrengths();
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java
index 6b1ca39..c74332c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java
@@ -74,7 +74,7 @@
}
});
mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
- TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY, TEST_PRIORITY,
+ TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY, TEST_PRIORITY,
TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
TEST_SLOT, TEST_SUB_ID);
}
@@ -154,7 +154,7 @@
SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
- TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY, TEST_PRIORITY,
+ TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY, TEST_PRIORITY,
null, info, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
TEST_SLOT, TEST_SUB_ID);
assertEquals(info, mSmsCbMessage.getCmasWarningInfo());
@@ -163,14 +163,14 @@
@Test
public void testEmergency() {
mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
- TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+ TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
TEST_SLOT, TEST_SUB_ID);
assertEquals(true, mSmsCbMessage.isEmergencyMessage());
mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
- TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+ TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
SmsCbMessage.MESSAGE_PRIORITY_NORMAL,
TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
TEST_SLOT, TEST_SUB_ID);
@@ -180,14 +180,14 @@
@Test
public void testIsEtws() {
mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
- TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+ TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
null, TEST_CMAS_INFO, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
TEST_SLOT, TEST_SUB_ID);
assertEquals(false, mSmsCbMessage.isEtwsMessage());
mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
- TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+ TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
TEST_SLOT, TEST_SUB_ID);
@@ -197,14 +197,14 @@
@Test
public void testIsCmas() {
mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
- TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+ TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
TEST_SLOT, TEST_SUB_ID);
assertEquals(false, mSmsCbMessage.isCmasMessage());
mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
- TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+ TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
null, TEST_CMAS_INFO, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
TEST_SLOT, TEST_SUB_ID);
@@ -216,7 +216,7 @@
assertEquals(false, mSmsCbMessage.needGeoFencingCheck());
mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
- TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+ TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
null, TEST_CMAS_INFO, 100, TEST_GEOS, TEST_RECEIVED_TIME,
TEST_SLOT, TEST_SUB_ID);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
index 8c855db..7c8c9d46 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
@@ -23,7 +23,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -241,26 +240,22 @@
return;
}
+ SmsMessage.SubmitPdu smsPdu;
String scAddress = null, destinationAddress = null;
String message = null;
boolean statusReportRequested = false;
- try {
- // null message, null destination
- SmsMessage.getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested);
- fail("Should throw NullPointerException");
- } catch (NullPointerException expected) {
- // expected
- }
+ // Null message, null destination
+ smsPdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress, message,
+ statusReportRequested);
+ assertNull(smsPdu);
message = "This is a test message";
- try {
- // non-null message
- SmsMessage.getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested);
- fail("Should throw NullPointerException");
- } catch (NullPointerException expected) {
- // expected
- }
+
+ // Non-null message, null destination
+ smsPdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress, message,
+ statusReportRequested);
+ assertNull(smsPdu);
if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
// TODO: temp workaround, OCTET encoding for EMS not properly supported
@@ -271,8 +266,8 @@
destinationAddress = "18004664411";
message = "This is a test message";
statusReportRequested = false;
- SmsMessage.SubmitPdu smsPdu =
- SmsMessage.getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested);
+ smsPdu = SmsMessage.getSubmitPdu(
+ scAddress, destinationAddress, message, statusReportRequested);
assertNotNull(smsPdu);
smsPdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress, (short)80,
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index 7614ffe..718f261 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -68,6 +68,7 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
+
public class SubscriptionManagerTest {
private SubscriptionManager mSm;
@@ -650,12 +651,12 @@
if (!isSupported()) return;
boolean enabled = executeWithShellPermissionAndDefault(false, mSm,
(sm) -> sm.isSubscriptionEnabled(mSubId));
- if (isDSDS()) {
- // Change it to a different value
- changeAndVerifySubscriptionEnabledValue(mSubId, !enabled);
- // Reset it back to original
- changeAndVerifySubscriptionEnabledValue(mSubId, enabled);
- }
+ // Enable or disable subscription may require users UX confirmation or may not be supported.
+ // Call APIs to make sure there's no crash.
+ executeWithShellPermissionAndDefault(false, mSm,
+ (sm) -> sm.setSubscriptionEnabled(mSubId, !enabled));
+ executeWithShellPermissionAndDefault(false, mSm,
+ (sm) -> sm.setSubscriptionEnabled(mSubId, enabled));
}
@Test
@@ -732,18 +733,6 @@
}
}
- private void changeAndVerifySubscriptionEnabledValue(int subId, boolean targetValue) {
- boolean changeSuccessfully = executeWithShellPermissionAndDefault(false, mSm,
- (sm) -> sm.setSubscriptionEnabled(subId, targetValue));
- if (!changeSuccessfully) {
- fail("Cannot change subscription " + subId
- + " from " + !targetValue + " to " + targetValue);
- }
- boolean res = executeWithShellPermissionAndDefault(targetValue, mSm,
- (sm) -> sm.isSubscriptionEnabled(subId));
- assertEquals(targetValue, res);
- }
-
private <T, U> T executeWithShellPermissionAndDefault(T defaultValue, U targetObject,
ShellIdentityUtils.ShellPermissionMethodHelper<T, U> helper) {
try {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index e8812cc..f48cf47 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -53,6 +53,7 @@
import android.telephony.Annotation.RadioPowerState;
import android.telephony.AvailableNetworkInfo;
import android.telephony.CallAttributes;
+import android.telephony.CallForwardingInfo;
import android.telephony.CallQuality;
import android.telephony.CarrierConfigManager;
import android.telephony.CellLocation;
@@ -164,6 +165,8 @@
private static final int MAX_FPLMN_NUM = 100;
private static final int MIN_FPLMN_NUM = 3;
+ private static final String TEST_FORWARD_NUMBER = "54321";
+
static {
EMERGENCY_NUMBER_SOURCE_SET = new HashSet<Integer>();
EMERGENCY_NUMBER_SOURCE_SET.add(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
@@ -513,6 +516,8 @@
mTelephonyManager.getSubscriptionId(defaultAccount);
mTelephonyManager.getCarrierConfig();
ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ (tm) -> tm.isDataConnectionEnabled());
+ ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
(tm) -> tm.isAnyRadioPoweredOn());
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
(tm) -> tm.resetIms(tm.getSlotIndex()));
@@ -527,6 +532,101 @@
}
TelephonyManager.getDefaultRespondViaMessageApplication(getContext(), false);
+ ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ (tm) -> tm.getPhoneCapability());
+ }
+
+ @Test
+ public void testGetCallForwarding() {
+ List<Integer> callForwardingReasons = new ArrayList<>();
+ callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
+ callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
+ callForwardingReasons.add(CallForwardingInfo.REASON_NO_REPLY);
+ callForwardingReasons.add(CallForwardingInfo.REASON_NOT_REACHABLE);
+ callForwardingReasons.add(CallForwardingInfo.REASON_ALL);
+ callForwardingReasons.add(CallForwardingInfo.REASON_ALL_CONDITIONAL);
+
+ Set<Integer> callForwardingStatus = new HashSet<Integer>();
+ callForwardingStatus.add(CallForwardingInfo.STATUS_INACTIVE);
+ callForwardingStatus.add(CallForwardingInfo.STATUS_ACTIVE);
+ callForwardingStatus.add(CallForwardingInfo.STATUS_FDN_CHECK_FAILURE);
+ callForwardingStatus.add(CallForwardingInfo.STATUS_UNKNOWN_ERROR);
+ callForwardingStatus.add(CallForwardingInfo.STATUS_NOT_SUPPORTED);
+
+ for (int callForwardingReasonToGet : callForwardingReasons) {
+ Log.d(TAG, "[testGetCallForwarding] callForwardingReasonToGet: "
+ + callForwardingReasonToGet);
+ CallForwardingInfo callForwardingInfo =
+ ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ (tm) -> tm.getCallForwarding(callForwardingReasonToGet));
+
+ assertNotNull(callForwardingInfo);
+ assertTrue(callForwardingStatus.contains(callForwardingInfo.getStatus()));
+ assertTrue(callForwardingReasons.contains(callForwardingInfo.getReason()));
+ callForwardingInfo.getNumber();
+ assertTrue(callForwardingInfo.getTimeoutSeconds() >= 0);
+ }
+ }
+
+ @Test
+ public void testSetCallForwarding() {
+ List<Integer> callForwardingReasons = new ArrayList<>();
+ callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
+ callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
+ callForwardingReasons.add(CallForwardingInfo.REASON_NO_REPLY);
+ callForwardingReasons.add(CallForwardingInfo.REASON_NOT_REACHABLE);
+ callForwardingReasons.add(CallForwardingInfo.REASON_ALL);
+ callForwardingReasons.add(CallForwardingInfo.REASON_ALL_CONDITIONAL);
+
+ // Enable Call Forwarding
+ for (int callForwardingReasonToEnable : callForwardingReasons) {
+ final CallForwardingInfo callForwardingInfoToEnable = new CallForwardingInfo(
+ CallForwardingInfo.STATUS_ACTIVE,
+ callForwardingReasonToEnable,
+ TEST_FORWARD_NUMBER,
+ 1 /** time seconds */);
+ Log.d(TAG, "[testSetCallForwarding] Enable Call Forwarding. Status: "
+ + CallForwardingInfo.STATUS_ACTIVE + " Reason: " + callForwardingReasonToEnable
+ + " Number: " + TEST_FORWARD_NUMBER + " Time Seconds: 1");
+ ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ (tm) -> tm.setCallForwarding(callForwardingInfoToEnable));
+ }
+
+ // Disable Call Forwarding
+ for (int callForwardingReasonToDisable : callForwardingReasons) {
+ final CallForwardingInfo callForwardingInfoToDisable = new CallForwardingInfo(
+ CallForwardingInfo.STATUS_INACTIVE,
+ callForwardingReasonToDisable,
+ TEST_FORWARD_NUMBER,
+ 1 /** time seconds */);
+ Log.d(TAG, "[testSetCallForwarding] Disable Call Forwarding. Status: "
+ + CallForwardingInfo.STATUS_INACTIVE + " Reason: "
+ + callForwardingReasonToDisable + " Number: " + TEST_FORWARD_NUMBER
+ + " Time Seconds: 1");
+ ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ (tm) -> tm.setCallForwarding(callForwardingInfoToDisable));
+ }
+ }
+
+ @Test
+ public void testGetCallWaitingStatus() {
+ Set<Integer> callWaitingStatus = new HashSet<Integer>();
+ callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_ACTIVE);
+ callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_INACTIVE);
+ callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+ callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+
+ int status = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getCallWaitingStatus());
+ assertTrue(callWaitingStatus.contains(status));
+ }
+
+ @Test
+ public void testSetCallWaitingStatus() {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ (tm) -> tm.setCallWaitingStatus(true));
+ ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+ (tm) -> tm.setCallWaitingStatus(false));
}
@Test
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
index 5b0492f..aa60c8e 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
@@ -33,6 +33,7 @@
import android.service.euicc.IDownloadSubscriptionCallback;
import android.service.euicc.IEraseSubscriptionsCallback;
import android.service.euicc.IEuiccService;
+import android.service.euicc.IEuiccServiceDumpResultCallback;
import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
import android.service.euicc.IGetEidCallback;
@@ -432,4 +433,25 @@
assertTrue(mCallback.isMethodCalled());
}
+
+ @Test
+ public void testDump() throws Exception {
+ mCountDownLatch = new CountDownLatch(1);
+
+ mEuiccServiceBinder.dump(new IEuiccServiceDumpResultCallback.Stub() {
+ @Override
+ public void onComplete(String logs) {
+ assertEquals(MockEuiccService.MOCK_DUMPED_LOG, logs);
+ mCountDownLatch.countDown();
+ }
+ });
+
+ try {
+ mCountDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+
+ assertTrue(mCallback.isMethodCalled());
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
index c952446..bfd1601 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
@@ -29,10 +29,13 @@
import android.telephony.euicc.EuiccManager;
import android.telephony.euicc.EuiccManager.OtaStatus;
+import java.io.PrintWriter;
+
/** Dummy implementation of {@link EuiccService} for testing. */
public class MockEuiccService extends EuiccService {
static String MOCK_EID = "89000000000000000000000000000000";
static String MOCK_OS_VERSION = "1.0";
+ static final String MOCK_DUMPED_LOG = "log messages";
interface IMockEuiccServiceCallback {
void setMethodCalled();
@@ -61,6 +64,12 @@
}
@Override
+ public void dump(PrintWriter printWriter) {
+ sMockEuiccServiceCallback.setMethodCalled();
+ printWriter.append(MOCK_DUMPED_LOG);
+ }
+
+ @Override
public void onStartOtaIfNecessary(int slotId, OtaStatusChangedCallback statusChangedCallback) {
sMockEuiccServiceCallback.setMethodCalled();
statusChangedCallback.onOtaStatusChanged(EuiccManager.EUICC_OTA_SUCCEEDED);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
index d5a61c8..fc478ef 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
@@ -66,6 +66,7 @@
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -780,6 +781,7 @@
}
+ @Ignore("RCS APIs not public yet")
@Test
public void testRcsManagerRegistrationCallback() throws Exception {
if (!ImsUtils.shouldTestImsService()) {
@@ -949,6 +951,7 @@
(m) -> m.unregisterImsRegistrationCallback(callback));
}
+ @Ignore("RCS APIs not public yet")
@Test
public void testRcsManagerRegistrationState() throws Exception {
if (!ImsUtils.shouldTestImsService()) {
@@ -1163,6 +1166,7 @@
}
}
+ @Ignore("RCS APIs not public yet")
@Test
public void testRcsCapabilityStatusCallback() throws Exception {
if (!ImsUtils.shouldTestImsService()) {
@@ -1560,10 +1564,10 @@
.build()));
// The RcsFeature is created when the ImsService is bound. If it wasn't created, then the
// Framework did not call it.
- sServiceConnector.getCarrierService().waitForLatchCountdown(
- TestImsService.LATCH_CREATE_RCS);
- sServiceConnector.getCarrierService().waitForLatchCountdown(
- TestImsService.LATCH_RCS_READY);
+ assertTrue("Did not receive createRcsFeature", sServiceConnector.getCarrierService()
+ .waitForLatchCountdown(TestImsService.LATCH_CREATE_RCS));
+ assertTrue("Did not receive RcsFeature#onReady", sServiceConnector.getCarrierService()
+ .waitForLatchCountdown(TestImsService.LATCH_RCS_READY));
// Make sure the RcsFeature was created in the test service.
assertNotNull("Device ImsService created, but TestDeviceImsService#createRcsFeature was not"
+ "called!", sServiceConnector.getCarrierService().getRcsFeature());
@@ -1576,10 +1580,10 @@
.build()));
// The MmTelFeature is created when the ImsService is bound. If it wasn't created, then the
// Framework did not call it.
- sServiceConnector.getCarrierService().waitForLatchCountdown(
- TestImsService.LATCH_CREATE_MMTEL);
- assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
- TestImsService.LATCH_MMTEL_READY));
+ assertTrue("Did not receive createMmTelFeature", sServiceConnector.getCarrierService()
+ .waitForLatchCountdown(TestImsService.LATCH_CREATE_MMTEL));
+ assertTrue("Did not receive MmTelFeature#onReady", sServiceConnector.getCarrierService()
+ .waitForLatchCountdown(TestImsService.LATCH_MMTEL_READY));
assertNotNull("ImsService created, but ImsService#createMmTelFeature was not called!",
sServiceConnector.getCarrierService().getMmTelFeature());
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
index 73e3b05..2eb16cd 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
@@ -25,6 +25,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,6 +40,7 @@
+ "-application.ims.iari.rcs.mnc000.mcc000.testService\"";
@Test
+ @Ignore("RCS APIs not public yet")
public void createParcelUnparcel() {
if (!ImsUtils.shouldTestImsService()) {
return;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
index 8ab1f78..755d439 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
@@ -123,53 +123,9 @@
ImsManager imsManager = getContext().getSystemService(ImsManager.class);
RcsUceAdapter uceAdapter = imsManager.getImsRcsManager(sTestSub).getUceAdapter();
assertNotNull("UCE adapter should not be null!", uceAdapter);
-
// requestCapabilities
ArrayList<Uri> numbers = new ArrayList<>(1);
numbers.add(TEST_NUMBER_URI);
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
- (m) -> m.requestCapabilities(Runnable::run, numbers,
- new RcsUceAdapter.CapabilitiesCallback() {
- }), ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- // unsupported is a valid fail cause.
- if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
- fail("request capabilities failed with code " + e.getCode());
- }
- }
-
- try {
- uceAdapter.requestCapabilities(Runnable::run, numbers,
- new RcsUceAdapter.CapabilitiesCallback() {});
- fail("requestCapabilities should require READ_PRIVILEGED_PHONE_STATE.");
- } catch (SecurityException e) {
- //expected
- }
-
- // getUcePublishState
- try {
- Integer result = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
- uceAdapter, RcsUceAdapter::getUcePublishState, ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- assertNotNull("result from getUcePublishState should not be null", result);
- } catch (SecurityException e) {
- fail("getUcePublishState should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- // unsupported is a valid fail cause.
- if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
- fail("getUcePublishState failed with code " + e.getCode());
- }
- }
- try {
- uceAdapter.getUcePublishState();
- fail("requestCapabilities should require READ_PRIVILEGED_PHONE_STATE.");
- } catch (SecurityException e) {
- //expected
- }
//isUceSettingEnabled
Boolean isUceSettingEnabledResult = null;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
index 9752258..3354158 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
@@ -16,13 +16,9 @@
package android.telephony.ims.cts;
-import android.telephony.ims.feature.CapabilityChangeRequest;
import android.telephony.ims.feature.RcsFeature;
-import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
-import java.util.List;
-
public class TestRcsFeature extends RcsFeature {
private static final String TAG = "CtsTestImsService";
@@ -30,9 +26,6 @@
private final TestImsService.RemovedListener mRemovedListener;
private final TestImsService.CapabilitiesSetListener mCapSetListener;
- private RcsImsCapabilities mCapabilities =
- new RcsImsCapabilities(RcsImsCapabilities.CAPABILITY_TYPE_NONE);
-
TestRcsFeature(TestImsService.ReadyListener readyListener,
TestImsService.RemovedListener listener,
TestImsService.CapabilitiesSetListener setListener) {
@@ -58,35 +51,4 @@
}
mRemovedListener.onRemoved();
}
-
-
- @Override
- public boolean queryCapabilityConfiguration(int capability, int radioTech) {
- if (ImsUtils.VDBG) {
- Log.d(TAG, "TestRcsFeature.queryCapabilityConfiguration called with capability: "
- + capability);
- }
- return mCapabilities.isCapable(capability);
- }
-
- @Override
- public void changeEnabledCapabilities(CapabilityChangeRequest request,
- CapabilityCallbackProxy c) {
- if (ImsUtils.VDBG) {
- Log.d(TAG, "TestRcsFeature.changeEnabledCapabilities");
- }
- List<CapabilityChangeRequest.CapabilityPair> pairs = request.getCapabilitiesToEnable();
- for (CapabilityChangeRequest.CapabilityPair pair : pairs) {
- if (pair.getRadioTech() == ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
- mCapabilities.addCapabilities(pair.getCapability());
- }
- }
- pairs = request.getCapabilitiesToDisable();
- for (CapabilityChangeRequest.CapabilityPair pair : pairs) {
- if (pair.getRadioTech() == ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
- mCapabilities.removeCapabilities(pair.getCapability());
- }
- }
- mCapSetListener.onSet();
- }
}
diff --git a/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 043d04f..98dbe52 100644
--- a/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -15,13 +15,16 @@
*/
package android.tethering.test;
+import static android.net.TetheringManager.TETHERING_WIFI;
+
import static org.junit.Assert.fail;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.ConnectivityManager;
+import android.net.TetheringManager;
+import android.net.TetheringManager.TetheringRequest;
import android.os.ConditionVariable;
import androidx.test.InstrumentationRegistry;
@@ -35,6 +38,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -43,7 +47,7 @@
private Context mContext;
- private ConnectivityManager mCM;
+ private TetheringManager mTM;
private TetherChangeReceiver mTetherChangeReceiver;
@@ -57,10 +61,10 @@
.getUiAutomation()
.adoptShellPermissionIdentity();
mContext = InstrumentationRegistry.getContext();
- mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mTM = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE);
mTetherChangeReceiver = new TetherChangeReceiver();
final IntentFilter filter = new IntentFilter(
- ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+ TetheringManager.ACTION_TETHER_STATE_CHANGED);
final Intent intent = mContext.registerReceiver(mTetherChangeReceiver, filter);
if (intent != null) mTetherChangeReceiver.onReceive(null, intent);
}
@@ -81,36 +85,37 @@
TetherState(Intent intent) {
mAvailable = intent.getStringArrayListExtra(
- ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+ TetheringManager.EXTRA_AVAILABLE_TETHER);
mActive = intent.getStringArrayListExtra(
- ConnectivityManager.EXTRA_ACTIVE_TETHER);
+ TetheringManager.EXTRA_ACTIVE_TETHER);
mErrored = intent.getStringArrayListExtra(
- ConnectivityManager.EXTRA_ERRORED_TETHER);
+ TetheringManager.EXTRA_ERRORED_TETHER);
}
}
@Override
public void onReceive(Context content, Intent intent) {
String action = intent.getAction();
- if (action.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)) {
+ if (action.equals(TetheringManager.ACTION_TETHER_STATE_CHANGED)) {
mResult.add(new TetherState(intent));
}
}
public final LinkedBlockingQueue<TetherState> mResult = new LinkedBlockingQueue<>();
- // This method expects either an event where one of the interfaces is active, or an event
- // where one of the interface is available followed by one where one of the interfaces is
- // active.
+ // This method expects either an event where one of the interfaces is active, or events
+ // where the interfaces are available followed by one event where one of the interfaces is
+ // active. Here is a typical example for wifi tethering:
+ // AVAILABLE(wlan0) -> AVAILABLE(wlan1) -> ACTIVATE(wlan1).
public void expectActiveTethering(String[] ifaceRegexs) {
- TetherState state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS);
- if (state == null) fail("Do not receive tethering state change broadcast");
-
- if (isIfaceActive(ifaceRegexs, state)) return;
-
- if (isIfaceAvailable(ifaceRegexs, state)) {
+ TetherState state = null;
+ while (true) {
state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS);
+ if (state == null) fail("Do not receive active state change broadcast");
+
if (isIfaceActive(ifaceRegexs, state)) return;
+
+ if (!isIfaceAvailable(ifaceRegexs, state)) break;
}
fail("Tethering is not actived, available ifaces: " + state.mAvailable.toString()
@@ -175,16 +180,15 @@
}
}
- private class OnStartTetheringCallback extends
- ConnectivityManager.OnStartTetheringCallback {
+ private class StartTetheringCallback extends TetheringManager.StartTetheringCallback {
@Override
public void onTetheringStarted() {
// Do nothing, TetherChangeReceiver will wait until it receives the broadcast.
}
@Override
- public void onTetheringFailed() {
- fail("startTethering fail");
+ public void onTetheringFailed(final int resultCode) {
+ fail("startTethering fail: " + resultCode);
}
}
@@ -206,18 +210,19 @@
@Test
public void testStartTetheringWithStateChangeBroadcast() throws Exception {
- if (!mCM.isTetheringSupported()) return;
+ if (!mTM.isTetheringSupported()) return;
- final String[] wifiRegexs = mCM.getTetherableWifiRegexs();
+ final String[] wifiRegexs = mTM.getTetherableWifiRegexs();
if (wifiRegexs.length == 0) return;
mTetherChangeReceiver.expectNoActiveTethering(0 /** timeout */);
- final OnStartTetheringCallback startTetheringCallback = new OnStartTetheringCallback();
- mCM.startTethering(ConnectivityManager.TETHERING_WIFI, true, startTetheringCallback);
+ final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+ mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(), c -> c.run(),
+ startTetheringCallback);
mTetherChangeReceiver.expectActiveTethering(wifiRegexs);
- mCM.stopTethering(ConnectivityManager.TETHERING_WIFI);
+ mTM.stopTethering(TETHERING_WIFI);
mTetherChangeReceiver.expectNoActiveTethering(DEFAULT_TIMEOUT_MS);
}
}