Merge "disable MediaPlayer2 tests" into pi-dev
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index e3b1916..cb3f51a 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -27,8 +27,6 @@
import string
import unicodedata
-CMD_DELAY = 1 # seconds
-
class ItsSession(object):
"""Controls a device over adb to run ITS scripts.
@@ -204,10 +202,6 @@
# TODO: Figure out why "--user 0" is needed, and fix the problem.
_run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
- _run(('%s shell am start --user 0 '
- 'com.android.cts.verifier/.camera.its.ItsTestActivity '
- '--activity-brought-to-front') % self.adb)
- time.sleep(CMD_DELAY)
_run(('%s shell am start-foreground-service --user 0 -t text/plain '
'-a %s') % (self.adb, self.INTENT_START))
@@ -911,11 +905,13 @@
Returns:
Nothing.
"""
+ ACTIVITY_START_WAIT = 1.5 # seconds
adb = "adb -s " + device_id
- # Start ItsTestActivity to prevent flaky
+ # Start ItsTestActivity to receive test results
cmd = "%s shell am start %s --activity-brought-to-front" % (adb, ItsSession.ITS_TEST_ACTIVITY)
_run(cmd)
+ time.sleep(ACTIVITY_START_WAIT)
# Validate/process results argument
for scene in results:
@@ -943,6 +939,20 @@
print "ITS command string might be too long! len:", len(cmd)
_run(cmd)
+def adb_log(device_id, msg):
+ """Send a log message to adb logcat
+
+ Args:
+ device_id: The ID string of the adb device
+ msg: the message string to be send to logcat
+
+ Returns:
+ Nothing.
+ """
+ adb = "adb -s " + device_id
+ cmd = "%s shell log -p i -t \"ItsTestHost\" %s" % (adb, msg)
+ _run(cmd)
+
def get_device_fingerprint(device_id):
""" Return the Build FingerPrint of the device that the test is running on.
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 24878a4..61c4917 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -420,6 +420,7 @@
msg = "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0)
print msg
+ its.device.adb_log(device_id, msg)
msg_short = "%s %s [%.1fs]" % (retstr, testname, t1-t0)
if test_failed:
summary += msg_short + "\n"
@@ -450,7 +451,9 @@
else ItsSession.RESULT_FAIL)
results[scene][ItsSession.SUMMARY_KEY] = summary_path
- print "Reporting ITS result to CtsVerifier"
+ msg = "Reporting ITS result to CtsVerifier"
+ print msg
+ its.device.adb_log(device_id, msg)
if merge_result_switch:
# results are modified by report_result
results_backup = copy.deepcopy(results)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index 8007983..7fa4e1a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -405,10 +405,10 @@
}
@Override
- protected void onPause() {
- super.onPause();
+ public void onDestroy() {
Log.d(TAG, "unregister ITS result receiver");
unregisterReceiver(mResultsReceiver);
+ super.onDestroy();
}
@Override
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
index 6c00d08..1f8b024 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
@@ -73,7 +73,7 @@
public CompatibilityTestSuite() {
try {
OptionSetter setter = new OptionSetter(this);
- setter.setOptionValue("config-patterns", ".*.config");
+ setter.setOptionValue("config-patterns", ".*\\.config");
setter.setOptionValue("skip-loading-config-jar", "true");
} catch (ConfigurationException e) {
// Should not happen
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
index c8deffd..e727d56 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
@@ -75,7 +75,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE.equals(intent.getAction())) {
- long token =
+ final long token =
intent.getLongExtra(BasicAdminReceiver.EXTRA_NETWORK_LOGS_BATCH_TOKEN,
FAKE_BATCH_TOKEN);
// Retrieve network logs.
@@ -94,7 +94,7 @@
};
private CountDownLatch mBatchCountDown;
- private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
+ private final ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
private int mBatchesRequested = 1;
@Override
@@ -174,7 +174,7 @@
// if DeviceAdminReceiver#onNetworkLogsAvailable() hasn't been triggered yet, wait for up to
// 3 minutes per batch just in case
- int timeoutMins = 3 * mBatchesRequested;
+ final int timeoutMins = 3 * mBatchesRequested;
mBatchCountDown.await(timeoutMins, TimeUnit.MINUTES);
LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mNetworkLogsReceiver);
if (mBatchCountDown.getCount() > 0) {
@@ -187,7 +187,7 @@
assertEquals("First event has the wrong id.", 0L, mNetworkEvents.get(0).getId());
// For each of the real URLs we have two events: one DNS and one connect. Dummy requests
// don't require DNS queries.
- int eventsExpected =
+ final int eventsExpected =
Math.min(FULL_LOG_BATCH_SIZE * mBatchesRequested,
2 * LOGGED_URLS_LIST.length + dummyReqNo);
verifyNetworkLogs(mNetworkEvents, eventsExpected);
@@ -279,10 +279,10 @@
/** Quickly generate loads of events by repeatedly connecting to a local server. */
private int generateDummyTraffic() throws IOException, InterruptedException {
- ServerSocket serverSocket = new ServerSocket(0);
- Thread serverThread = startDummyServer(serverSocket);
+ final ServerSocket serverSocket = new ServerSocket(0);
+ final Thread serverThread = startDummyServer(serverSocket);
- int reqNo = makeDummyRequests(serverSocket.getLocalPort());
+ final int reqNo = makeDummyRequests(serverSocket.getLocalPort());
serverSocket.close();
serverThread.join();
@@ -306,29 +306,27 @@
}
private Thread startDummyServer(ServerSocket serverSocket) throws InterruptedException {
- Thread serverThread = new Thread(() -> {
- while (true) {
+ final Thread serverThread = new Thread(() -> {
+ while (!serverSocket.isClosed()) {
try {
- Socket socket = serverSocket.accept();
+ final Socket socket = serverSocket.accept();
// Consume input.
- BufferedReader input =
+ final BufferedReader input =
new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
do {
line = input.readLine();
} while (line != null && !line.equals(""));
// Return minimum valid response.
- PrintStream output = new PrintStream(socket.getOutputStream());
+ final PrintStream output = new PrintStream(socket.getOutputStream());
output.println("HTTP/1.0 200 OK");
output.println("Content-Length: 0");
output.println();
output.flush();
output.close();
} catch (IOException e) {
- if (mBatchCountDown.getCount() > 0) {
+ if (!serverSocket.isClosed()) {
Log.w(TAG, "Failed to serve connection", e);
- } else {
- break;
}
}
}
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java
index e4f5176..98e4b7a 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java
@@ -34,7 +34,7 @@
if (execCommandAndFind(CMD_TOP, SYSTEM_SERVER) != null) {
CLog.logAndDisplay(LogLevel.INFO, "stop server");
getDevice().executeShellCommand("stop");
- Thread.sleep(3000); // wait for 3 seconds to stop.
+ Thread.sleep(10000); // wait for 10 seconds to stop.
assertTrue("system_server failed to stop",
!execCommandAndGet(CMD_TOP).contains(SYSTEM_SERVER));
}
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
index a3751f5..c62d85e 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -27,7 +27,7 @@
final String destArg = dest == null || dest.isEmpty() ? "" : "-p " + dest;
final IncidentProto dump = getDump(IncidentProto.parser(), "incident " + destArg + " 2>/dev/null");
- SystemPropertiesTest.verifySystemPropertiesProto(dump.getSystemProperties(), filterLevel, mCtsBuild);
+ SystemPropertiesTest.verifySystemPropertiesProto(dump.getSystemProperties(), filterLevel);
StackTraceIncidentTest.verifyBackTraceProto(dump.getNativeTraces(), filterLevel);
StackTraceIncidentTest.verifyBackTraceProto(dump.getHalTraces(), filterLevel);
diff --git a/hostsidetests/incident/src/com/android/server/cts/SystemPropertiesTest.java b/hostsidetests/incident/src/com/android/server/cts/SystemPropertiesTest.java
index 28e1886..8dc4722 100644
--- a/hostsidetests/incident/src/com/android/server/cts/SystemPropertiesTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/SystemPropertiesTest.java
@@ -24,18 +24,16 @@
public class SystemPropertiesTest extends ProtoDumpTestCase {
private static final String TAG = "SystemPropertiesTest";
- static void verifySystemPropertiesProto(SystemPropertiesProto properties,
- final int filterLevel, IBuildInfo ctsBuild) {
+ static void verifySystemPropertiesProto(SystemPropertiesProto properties, final int filterLevel) {
// check local tagged field
if (filterLevel == PRIVACY_LOCAL) {
assertTrue(properties.getExtraPropertiesCount() > 0);
} else {
assertEquals(0, properties.getExtraPropertiesCount());
}
- // check explicit tagged field
- assertEquals(filterLevel == PRIVACY_AUTO, properties.getDalvikVm().getHeapmaxfree().isEmpty());
- // check automatic tagged field
- assertEquals(ctsBuild.getBuildId(),
- properties.getRo().getBuild().getVersion().getIncremental());
+ // check explicit tagged field, persist.sys.timezone is public prop.
+ assertEquals(filterLevel == PRIVACY_AUTO, properties.getPersist().getSysTimezone().isEmpty());
+ // check automatic tagged field, ro.build.display.id is public prop.
+ assertFalse(properties.getRo().getBuild().getDisplayId().isEmpty());
}
}
diff --git a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
index d878342..2e0d0a5 100644
--- a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
+++ b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
@@ -73,8 +73,8 @@
public void testDevHwRandomPermissions() throws Exception {
// This test asserts that, if present, /dev/hw_random must:
- // 1. Be owned by UID root and GID system
- // 2. Have file permissions 0440 (only readable, and only by owner and group). The reason
+ // 1. Be owned by UID root
+ // 2. Not allow any world read, write, or execute permissions. The reason
// for being not readable by all/other is to avoid apps reading from this device.
// Firstly, /dev/hw_random is not public API for apps. Secondly, apps might erroneously
// use the output of Hardware RNG as trusted random output. Android does not trust output
@@ -96,9 +96,9 @@
fail("Unexpected output from " + command + ": \"" + output + "\"");
}
String[] outputWords = output.split("\\s");
- assertEquals("Wrong file mode on " + HW_RNG_DEVICE, "cr--r-----", outputWords[0]);
+ assertEquals("Wrong device type on " + HW_RNG_DEVICE, "c", outputWords[0].substring(0, 1));
+ assertEquals("Wrong world file mode on " + HW_RNG_DEVICE, "---", outputWords[0].substring(7));
assertEquals("Wrong owner of " + HW_RNG_DEVICE, "root", outputWords[2]);
- assertEquals("Wrong group of " + HW_RNG_DEVICE, "system", outputWords[3]);
assertEquals("Wrong device major on " + HW_RNG_DEVICE, "10,", outputWords[4]);
assertEquals("Wrong device minor on " + HW_RNG_DEVICE, "183", outputWords[5]);
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index 6cada58..fbba092 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -155,6 +155,7 @@
devicePlatFcFile = getDeviceFile(mDevice, cachedDevicePlatFcFiles,
"/system/etc/selinux/plat_file_contexts", "plat_file_contexts");
if (mDevice.doesFileExist("/vendor/etc/selinux/nonplat_file_contexts")){
+ // Old nonplat_* naming can be present if a framework-only OTA was done.
deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
"/vendor/etc/selinux/nonplat_file_contexts", "nonplat_file_contexts");
} else {
@@ -165,7 +166,7 @@
devicePlatFcFile = getDeviceFile(mDevice, cachedDevicePlatFcFiles,
"/plat_file_contexts", "plat_file_contexts");
deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
- "/nonplat_file_contexts", "nonplat_file_contexts");
+ "/vendor_file_contexts", "vendor_file_contexts");
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 71d600a..809d682 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertTrue;
import android.os.WakeLockLevelEnum;
+import android.platform.test.annotations.RestrictedBuildTest;
import com.android.internal.os.StatsdConfigProto.FieldMatcher;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
@@ -228,6 +229,7 @@
assertTrue("found uid " + uid, found);
}
+ @RestrictedBuildTest
public void testCpuTimePerUidFreq() throws Exception {
StatsdConfig.Builder config = getPulledConfig();
FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
new file mode 100644
index 0000000..0f2c0e8
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.utils.AsyncUtils;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.Debug;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.widget.Button;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+// Test that an AccessibilityService can display an accessibility overlay
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityOverlayTest {
+
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+ InstrumentedAccessibilityService mService;
+
+ @BeforeClass
+ public static void oneTimeSetUp() {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation
+ .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ sUiAutomation.setServiceInfo(info);
+ }
+
+ @Before
+ public void setUp() {
+ mService = StubAccessibilityButtonService.enableSelf(sInstrumentation);
+ }
+
+ @After
+ public void tearDown() {
+ mService.runOnServiceSync(() -> mService.disableSelf());
+ }
+
+ @Test
+ public void testA11yServiceShowsOverlay_shouldAppear() throws Exception {
+ final Button button = new Button(mService);
+ button.setText("Button");
+ final String overlayTitle = "Overlay title";
+
+ sUiAutomation.executeAndWaitForEvent(() -> mService.runOnServiceSync(() -> {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+ params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+ params.setTitle(overlayTitle);
+ mService.getSystemService(WindowManager.class).addView(button, params);
+ }), (event) -> findOverlayWindow() != null, AsyncUtils.DEFAULT_TIMEOUT_MS);
+
+ assertTrue(TextUtils.equals(findOverlayWindow().getTitle(), overlayTitle));
+ }
+
+ private AccessibilityWindowInfo findOverlayWindow() {
+ List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+ for (int i = 0; i < windows.size(); i++) {
+ AccessibilityWindowInfo window = windows.get(i);
+ if (window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
+ return window;
+ }
+ }
+ return null;
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
index 09328fa..53a70b3 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.accessibilityservice.cts;
import android.accessibilityservice.AccessibilityService;
diff --git a/tests/autofillservice/res/layout/fat_activity.xml b/tests/autofillservice/res/layout/fat_activity.xml
index 9b4a8b6..2efd33c 100644
--- a/tests/autofillservice/res/layout/fat_activity.xml
+++ b/tests/autofillservice/res/layout/fat_activity.xml
@@ -17,6 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/root"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:focusable="true"
@@ -136,10 +137,10 @@
</LinearLayout>
- <View
+ <view class="android.autofillservice.cts.FatActivity$MyView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autofillHints="importantAmI">
- </View>
+ </view>
</LinearLayout>
diff --git a/tests/autofillservice/res/layout/list_item.xml b/tests/autofillservice/res/layout/list_item.xml
index d7f0875..50364a2 100644
--- a/tests/autofillservice/res/layout/list_item.xml
+++ b/tests/autofillservice/res/layout/list_item.xml
@@ -22,6 +22,5 @@
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:background="#ffffffff">
+ android:minHeight="?android:attr/listPreferredItemHeightSmall">
</TextView>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
index da684d6..8ec9a3e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
@@ -16,8 +16,6 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.getContext;
-
import android.view.View;
import org.junit.Before;
@@ -38,12 +36,10 @@
new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class, false);
protected LoginActivity mActivity;
- protected boolean mCanPassKeys;
@Before
public void setActivity() {
mActivity = mActivityRule.getActivity();
- mCanPassKeys = !Helper.isAutofillWindowFullScreen(getContext());
}
/**
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
index 3becd9b..642e372 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
@@ -67,6 +67,8 @@
*/
@Test
public final void testTapLink_changeOrientationThenTapBack() throws Exception {
+ assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+
final int width = mUiBot.getDevice().getDisplayWidth();
final int heigth = mUiBot.getDevice().getDisplayHeight();
final int min = Math.min(width, heigth);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
index 2e964b1..451b002 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
@@ -21,6 +21,7 @@
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.content.IntentSender;
+import android.os.SystemClock;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -43,6 +44,12 @@
runShellCommand("cmd autofill set max_visible_datasets %s", sMaxDatasets);
}
+ private static void sendKeyEvents(String keyCode) {
+ runShellCommand("input keyevent " + keyCode);
+ // TODO wait mechanism for window size change
+ SystemClock.sleep(200);
+ }
+
@Test
public void testFilter() throws Exception {
final String aa = "Two A's";
@@ -99,10 +106,6 @@
@Test
public void testFilter_usingKeyboard() throws Exception {
- if (!mCanPassKeys) {
- return;
- }
-
final String aa = "Two A's";
final String ab = "A and B";
final String b = "Only B";
@@ -133,26 +136,26 @@
mUiBot.assertDatasets(aa, ab, b);
// Only two datasets start with 'a'
- runShellCommand("input keyevent KEYCODE_A");
+ sendKeyEvents("KEYCODE_A");
mUiBot.assertDatasets(aa, ab);
// Only one dataset start with 'aa'
- runShellCommand("input keyevent KEYCODE_A");
+ sendKeyEvents("KEYCODE_A");
mUiBot.assertDatasets(aa);
// Only two datasets start with 'a'
- runShellCommand("input keyevent KEYCODE_DEL");
+ sendKeyEvents("KEYCODE_DEL");
mUiBot.assertDatasets(aa, ab);
// With no filter text all datasets should be shown
- runShellCommand("input keyevent KEYCODE_DEL");
+ sendKeyEvents("KEYCODE_DEL");
mUiBot.assertDatasets(aa, ab, b);
// No dataset start with 'aaa'
final MyAutofillCallback callback = mActivity.registerCallback();
- runShellCommand("input keyevent KEYCODE_A");
- runShellCommand("input keyevent KEYCODE_A");
- runShellCommand("input keyevent KEYCODE_A");
+ sendKeyEvents("KEYCODE_A");
+ sendKeyEvents("KEYCODE_A");
+ sendKeyEvents("KEYCODE_A");
callback.assertUiHiddenEvent(mActivity.getUsername());
mUiBot.assertNoDatasets();
}
@@ -461,11 +464,11 @@
mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
// All datasets start with 'a'
- runShellCommand("input keyevent KEYCODE_A");
+ sendKeyEvents("KEYCODE_A");
mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
// Only the regex datasets should start with 'ab'
- runShellCommand("input keyevent KEYCODE_B");
+ sendKeyEvents("KEYCODE_B");
mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
index 56b5cff..567aeba 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
@@ -22,6 +22,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.app.assist.AssistStructure;
import android.util.Log;
import android.view.autofill.AutofillId;
@@ -77,6 +79,8 @@
@Test
public void testDoNotRestoreDuplicateAutofillIds() throws Exception {
+ assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+
enableService();
sReplier.addResponse(new CannedFillResponse.Builder()
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
index b3e064e..8a53f4a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
@@ -25,10 +25,13 @@
import static com.google.common.truth.Truth.assertThat;
+import android.content.Context;
import android.os.Bundle;
+import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
+import android.widget.LinearLayout;
/**
* An activity containing mostly widgets that should be removed from an auto-fill structure to
@@ -41,6 +44,7 @@
static final String ID_INPUT_CONTAINER = "input_container";
static final String ID_IMAGE = "image";
static final String ID_IMPORTANT_IMAGE = "important_image";
+ static final String ID_ROOT = "root";
static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
"not_important_container_excluding_descendants";
@@ -63,6 +67,7 @@
static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD =
"not_important_container_mixed_descendants_grand_child";
+ private LinearLayout mRoot;
private EditText mCaptcha;
private EditText mInput;
private ImageView mImage;
@@ -80,7 +85,7 @@
private View mNotImportantContainerMixedDescendantsChild;
private View mNotImportantContainerMixedDescendantsGrandChild;
- private View mViewWithAutofillHints;
+ private MyView mViewWithAutofillHints;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -88,6 +93,7 @@
setContentView(R.layout.fat_activity);
+ mRoot = findViewById(R.id.root);
mCaptcha = findViewById(R.id.captcha);
mInput = findViewById(R.id.input);
mImage = findViewById(R.id.image);
@@ -114,10 +120,11 @@
mNotImportantContainerMixedDescendantsGrandChild = findViewById(
R.id.not_important_container_mixed_descendants_grand_child);
- mViewWithAutofillHints = findViewByAutofillHint(this, "importantAmI");
+ mViewWithAutofillHints = (MyView) findViewByAutofillHint(this, "importantAmI");
assertThat(mViewWithAutofillHints).isNotNull();
// Sanity check for importantForAutofill modes
+ assertThat(mRoot.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
assertThat(mInput.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
assertThat(mCaptcha.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
assertThat(mImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
@@ -157,4 +164,18 @@
v.visit(mInput);
});
}
+
+ /**
+ * Custom view that defines an autofill type so autofill hints are set on {@code ViewNode}.
+ */
+ public static class MyView extends View {
+ public MyView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public int getAutofillType() {
+ return AUTOFILL_TYPE_TEXT;
+ }
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
index 9e458fd4..28ecdf7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
@@ -30,9 +30,11 @@
import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS;
import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD;
import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD;
-import static android.autofillservice.cts.Helper.assertNumberOfChildren;
+import static android.autofillservice.cts.FatActivity.ID_ROOT;
+import static android.autofillservice.cts.Helper.findNodeByAutofillHint;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.Helper.findNodeByText;
+import static android.autofillservice.cts.Helper.getNumberNodes;
import static android.autofillservice.cts.Helper.importantForAutofillAsString;
import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
@@ -43,7 +45,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
@@ -61,7 +62,7 @@
new AutofillActivityTestRule<FatActivity>(FatActivity.class);
private FatActivity mFatActivity;
- private AssistStructure mStructure;
+ private ViewNode mRoot;
@Before
public void setActivity() {
@@ -69,7 +70,7 @@
}
@Test
- public void testNoContainers() throws Exception {
+ public void testAutomaticRequest() throws Exception {
// Set service.
enableService();
@@ -79,13 +80,22 @@
// Trigger auto-fill.
mFatActivity.onInput((v) -> v.requestFocus());
final FillRequest fillRequest = sReplier.getNextFillRequest();
- mStructure = fillRequest.structure;
mUiBot.assertNoDatasetsEver();
- // TODO: should only have X children, but there is an extra
- // TextView that's probably coming from the title. For now we're just ignoring it, but
- // ideally we should change the .xml to exclude it.
- assertNumberOfChildren(fillRequest.structure, 9);
+ mRoot = findNodeByResourceId(fillRequest.structure, ID_ROOT);
+ /*
+ * Should have 8 nodes:
+ *
+ * 1. root
+ * 2. important_image
+ * 3. label without id but marked as important
+ * 4. input_container
+ * 5. input
+ * 6. important_container_excluding_descendants
+ * 7. not_important_container_mixed_descendants_child
+ * 8. view (My View) without id but with hints
+ */
+ assertThat(getNumberNodes(mRoot)).isEqualTo(8);
// Should not have ImageView...
assertThat(findNodeByResourceId(fillRequest.structure, ID_IMAGE)).isNull();
@@ -108,28 +118,31 @@
assertThat(input.getIdEntry()).isEqualTo(ID_INPUT);
// Make sure a non-important container can exclude descendants
- assertThat(findNodeByResourceId(fillRequest.structure,
+ assertThat(findNodeByResourceId(mRoot,
ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS)).isNull();
- assertThat(findNodeByResourceId(fillRequest.structure,
+ assertThat(findNodeByResourceId(mRoot,
ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD)).isNull();
- assertThat(findNodeByResourceId(fillRequest.structure,
+ assertThat(findNodeByResourceId(mRoot,
ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD)).isNull();
// Make sure an important container can exclude descendants
assertNodeExists(ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS,
IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
- assertThat(findNodeByResourceId(fillRequest.structure,
+ assertThat(findNodeByResourceId(mRoot,
ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD)).isNull();
- assertThat(findNodeByResourceId(fillRequest.structure,
+ assertThat(findNodeByResourceId(mRoot,
ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD)).isNull();
// Make sure an intermediary descendant can be excluded
- assertThat(findNodeByResourceId(fillRequest.structure,
+ assertThat(findNodeByResourceId(mRoot,
ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS)).isNull();
assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD,
IMPORTANT_FOR_AUTOFILL_YES);
- assertThat(findNodeByResourceId(fillRequest.structure,
+ assertThat(findNodeByResourceId(mRoot,
ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD)).isNull();
+
+ // Make sure entry with hints is always shown
+ assertThat(findNodeByAutofillHint(mRoot, "importantAmI")).isNotNull();
}
@Test
@@ -143,13 +156,31 @@
// Trigger autofill.
mFatActivity.onInput((v) -> mFatActivity.getAutofillManager().requestAutofill(v));
final FillRequest fillRequest = sReplier.getNextFillRequest();
- mStructure = fillRequest.structure;
mUiBot.assertNoDatasetsEver();
- // TODO: should only have X children, but there is an extra
- // TextView that's probably coming from the title. For now we're just ignoring it, but
- // ideally we should change the .xml to exclude it.
- assertNumberOfChildren(fillRequest.structure, 28);
+ mRoot = findNodeByResourceId(fillRequest.structure, ID_ROOT);
+ /*
+ * Should have 18 nodes:
+ * 1. root
+ * 2. layout without id
+ * 3. label without id
+ * 4. input_container
+ * 5. input
+ * 6. captcha
+ * 7. image
+ * 8. important_image
+ * 9. not_important_container_excluding_descendants
+ * 10.not_important_container_excluding_descendants_child
+ * 11.not_important_container_excluding_descendants_grand_child
+ * 12.important_container_excluding_descendants
+ * 13.important_container_excluding_descendants_child
+ * 14.important_container_excluding_descendants_grand_child
+ * 15.not_important_container_mixed_descendants
+ * 16.not_important_container_mixed_descendants_child
+ * 17.not_important_container_mixed_descendants_grand_child
+ * 18.view (My View) without id but with hints
+ */
+ assertThat(getNumberNodes(mRoot)).isEqualTo(18);
// Assert all nodes are present
assertNodeExists(ID_IMAGE, IMPORTANT_FOR_AUTOFILL_NO);
@@ -186,15 +217,18 @@
IMPORTANT_FOR_AUTOFILL_YES);
assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD,
IMPORTANT_FOR_AUTOFILL_NO);
+ assertNode(findNodeByAutofillHint(mRoot, "importantAmI"), IMPORTANT_FOR_AUTOFILL_AUTO);
}
private ViewNode assertNodeExists(String resourceId, int expectedImportantForAutofill) {
- final ViewNode node = findNodeByResourceId(mStructure, resourceId);
+ assertWithMessage("root node not set").that(mRoot).isNotNull();
+ final ViewNode node = findNodeByResourceId(mRoot, resourceId);
+ assertWithMessage("no node with resource id '%s'", resourceId).that(node).isNotNull();
return assertNode(node, resourceId, expectedImportantForAutofill);
}
private ViewNode assertNodeWithTextExists(String text, int expectedImportantForAutofill) {
- final ViewNode node = findNodeByText(mStructure, text);
+ final ViewNode node = findNodeByText(mRoot, text);
return assertNode(node, text, expectedImportantForAutofill);
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
index e559617..4196870 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
@@ -412,8 +412,9 @@
mUiBot.pressBack(); // dismiss autofill
mUiBot.pressBack(); // dismiss keyboard
mUiBot.pressBack(); // dismiss task
- assertThat(mActivity.getWindow().getDecorView().hasWindowFocus()).isTrue();
mUiBot.assertShownByRelativeId(ID_USERNAME);
+ assertWithMessage("root window has no focus")
+ .that(mActivity.getWindow().getDecorView().hasWindowFocus()).isTrue();
// ...and trigger save
// Set credentials...
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index c3af0b1..0a6cfc7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -137,6 +137,10 @@
return id.equals(node.getText());
};
+ private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> {
+ return hasHint(node.getAutofillHints(), id);
+ };
+
private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
final String className = node.getClassName();
if (!className.equals("android.webkit.WebView")) return false;
@@ -315,6 +319,14 @@
}
/**
+ * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
+ * found.
+ */
+ static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
+ return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
+ }
+
+ /**
* Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
* not found.
*/
@@ -644,7 +656,7 @@
}
/**
- * Gets the total number of nodes in an structure, including all descendants.
+ * Gets the total number of nodes in an structure.
*/
static int getNumberNodes(AssistStructure structure) {
int count = 0;
@@ -660,7 +672,7 @@
/**
* Gets the total number of nodes in an node, including all descendants and the node itself.
*/
- private static int getNumberNodes(ViewNode node) {
+ static int getNumberNodes(ViewNode node) {
int count = 1;
final int childrenSize = node.getChildCount();
if (childrenSize > 0) {
@@ -730,13 +742,20 @@
}
/**
- * Check if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi
+ * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi.
*/
public static boolean isAutofillWindowFullScreen(Context context) {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
}
/**
+ * Checks if screen orientation can be changed.
+ */
+ public static boolean isRotationSupported(Context context) {
+ return !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ }
+
+ /**
* Uses Shell command to get the Autofill logging level.
*/
public static String getLoggingLevel() {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 2457b19..475abc5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -30,6 +30,7 @@
import static android.autofillservice.cts.Helper.assertValue;
import static android.autofillservice.cts.Helper.dumpStructure;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.Helper.isAutofillWindowFullScreen;
import static android.autofillservice.cts.Helper.setUserComplete;
import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_CLASS;
import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_PACKAGE;
@@ -365,6 +366,7 @@
// TODO: currently disabled because the screenshot contains elements external to the
// activity that can change (for exmaple, clock), which causes flakiness to the test.
final boolean compareBitmaps = false;
+ final boolean pickerAndViewBoundsMatches = !isAutofillWindowFullScreen(mContext);
// Set service.
enableService();
@@ -389,8 +391,10 @@
Log.v(TAG,
"Username1 at " + usernameBoundaries1 + "; picker at " + usernamePickerBoundaries1);
// TODO(b/37566627): assertions below might be too aggressive - use range instead?
- assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
- assertThat(usernamePickerBoundaries1.left).isEqualTo(usernameBoundaries1.left);
+ if (pickerAndViewBoundsMatches) {
+ assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
+ assertThat(usernamePickerBoundaries1.left).isEqualTo(usernameBoundaries1.left);
+ }
// Move to password
final Rect passwordBoundaries1 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
@@ -401,8 +405,10 @@
Log.v(TAG,
"Password1 at " + passwordBoundaries1 + "; picker at " + passwordPickerBoundaries1);
// TODO(b/37566627): assertions below might be too aggressive - use range instead?
- assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
- assertThat(passwordPickerBoundaries1.left).isEqualTo(passwordBoundaries1.left);
+ if (pickerAndViewBoundsMatches) {
+ assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
+ assertThat(passwordPickerBoundaries1.left).isEqualTo(passwordBoundaries1.left);
+ }
// Then back to username
final Rect usernameBoundaries2 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
@@ -2422,8 +2428,8 @@
// Make sure all datasets are shown.
// TODO: improve assertDatasets() so it supports scrolling, and assert all of them are
- // shown
- mUiBot.assertDatasets("DS-1", "DS-2", "DS-3");
+ // shown. In fullscreen there are 4 items, otherwise there are 3 items.
+ mUiBot.assertDatasetsContains("DS-1", "DS-2", "DS-3");
// TODO: once it supports scrolling, selects the last dataset and asserts it's filled.
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
index d9f0a81..3ea8f36 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
@@ -33,6 +33,8 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeTrue;
+
import android.app.PendingIntent;
import android.app.assist.AssistStructure;
import android.content.Intent;
@@ -123,6 +125,8 @@
@Test
public void testDatasetAuthResponseWhileAutofilledAppIsLifecycled() throws Exception {
+ assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+
// Set service.
enableService();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
index 46ca5f6..b24b56d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
@@ -1139,6 +1139,7 @@
* Tests scenario where service explicitly indicates which button is used to save.
*/
private void explicitySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
+ final boolean testBitmap = false;
startActivity();
mActivity.setAutoCommit(false);
mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
@@ -1164,7 +1165,10 @@
// Take a screenshot to make sure button doesn't disappear.
final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT");
- final Bitmap screenshotBefore = mActivity.takeScreenshot(mActivity.mCommit);
+ // Disable unnecessary screenshot tests as takeScreenshot() fails on some device.
+
+ final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
+ : null;
// Save it...
mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
@@ -1174,13 +1178,16 @@
// Make sure save button is showning (it was removed on earlier versions of the feature)
final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT");
- final Bitmap screenshotAfter = mActivity.takeScreenshot(mActivity.mCommit);
+ final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
+ : null;
// ... and assert results
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
- Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
+ if (testBitmap) {
+ Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
+ }
}
@Override
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 31ca017..17819fe 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -68,6 +68,7 @@
private static final String TAG = "AutoFillCtsUiBot";
private static final String RESOURCE_ID_DATASET_PICKER = "autofill_dataset_picker";
+ private static final String RESOURCE_ID_DATASET_HEADER = "autofill_dataset_header";
private static final String RESOURCE_ID_SAVE_SNACKBAR = "autofill_save";
private static final String RESOURCE_ID_SAVE_ICON = "autofill_save_icon";
private static final String RESOURCE_ID_SAVE_TITLE = "autofill_save_title";
@@ -95,6 +96,8 @@
static final BySelector DATASET_PICKER_SELECTOR = By.res("android", RESOURCE_ID_DATASET_PICKER);
private static final BySelector SAVE_UI_SELECTOR = By.res("android", RESOURCE_ID_SAVE_SNACKBAR);
+ private static final BySelector DATASET_HEADER_SELECTOR =
+ By.res("android", RESOURCE_ID_DATASET_HEADER);
private static final boolean DONT_DUMP_ON_ERROR = false;
private static final boolean DUMP_ON_ERROR = true;
@@ -162,7 +165,7 @@
}
/**
- * Asserts the dataset chooser is shown and contains the given datasets.
+ * Asserts the dataset chooser is shown and contains exactly the given datasets.
*
* @return the dataset picker object.
*/
@@ -174,7 +177,20 @@
}
/**
+ * Asserts the dataset chooser is shown and contains the given datasets.
+ *
+ * @return the dataset picker object.
+ */
+ UiObject2 assertDatasetsContains(String...names) throws Exception {
+ final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+ assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
+ .containsAllIn(Arrays.asList(names)).inOrder();
+ return picker;
+ }
+
+ /**
* Asserts the dataset chooser is shown and contains the given datasets, header, and footer.
+ * <p>In fullscreen, header view is not under R.id.autofill_dataset_picker.
*
* @return the dataset picker object.
*/
@@ -183,7 +199,15 @@
final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
final List<String> expectedChild = new ArrayList<>();
if (header != null) {
- expectedChild.add(header);
+ if (Helper.isAutofillWindowFullScreen(mContext)) {
+ final UiObject2 headerView = waitForObject(DATASET_HEADER_SELECTOR,
+ UI_DATASET_PICKER_TIMEOUT);
+ assertWithMessage("fullscreen wrong dataset header")
+ .that(getChildrenAsText(headerView))
+ .containsExactlyElementsIn(Arrays.asList(header)).inOrder();
+ } else {
+ expectedChild.add(header);
+ }
}
expectedChild.addAll(Arrays.asList(names));
if (footer != null) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
index 8028f1d..5fd507f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
@@ -33,6 +33,8 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeTrue;
+
import android.app.assist.AssistStructure.ViewNode;
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
@@ -107,7 +109,7 @@
@Test
public void testAutofillAsync() throws Exception {
- if (mCompatMode) return;
+ skipTestOnCompatMode();
autofillTest(false);
}
@@ -288,7 +290,7 @@
@Test
public void testAutofillManuallyOneDataset() throws Exception {
- if (mCompatMode) return; // TODO(b/73557072): not supported yet
+ skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
// Set service.
enableService();
@@ -323,7 +325,7 @@
}
private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
- if (mCompatMode) return; // TODO(b/73557072): not supported yet
+ skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
// Set service.
enableService();
@@ -752,12 +754,15 @@
* Asserts the dataset picker is properly displayed in a give line.
*/
protected void assertDatasetShown(Line line, String... expectedDatasets) throws Exception {
+ boolean autofillViewBoundsMatches = !Helper.isAutofillWindowFullScreen(mContext);
final Rect pickerBounds = mUiBot.assertDatasets(expectedDatasets).getVisibleBounds();
final Rect fieldBounds = line.getAbsCoordinates();
- assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
- fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
- assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s", pickerBounds,
- fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
+ if (autofillViewBoundsMatches) {
+ assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
+ fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
+ assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s",
+ pickerBounds, fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
+ }
}
protected void assertLabel(ViewNode node, String expectedValue) {
@@ -774,4 +779,9 @@
assertThat(urlBar.getWebDomain()).isNull();
assertThat(urlBar.getWebScheme()).isNull();
}
+
+
+ private void skipTestOnCompatMode() {
+ assumeTrue("test not applicable when on compat mode", !mCompatMode);
+ }
}
diff --git a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
index 6fca9ad..2e42e21 100644
--- a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
+++ b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
@@ -50,6 +50,9 @@
if (!isBackupSupported()) {
return;
}
+ // Launch the main activity so the app qualifies for backup.
+ createTestFileOfSize(BACKUP_APP_NAME, 1);
+
String separator = markLogcat();
exec("bmgr backupnow " + BACKUP_APP_NAME);
waitForLogcat(TIMEOUT_SECONDS, separator,
diff --git a/tests/framework/base/activitymanager/AndroidManifest.xml b/tests/framework/base/activitymanager/AndroidManifest.xml
index 6227834..a0d302d 100644
--- a/tests/framework/base/activitymanager/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -74,6 +74,9 @@
<activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$SingleTopActivity"
android:launchMode="singleTop" />
+ <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$ConfigChangeHandlingActivity"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density" />
+
<activity android:name="android.server.am.StartActivityTests$TestActivity2" />
</application>
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
index a36079c..68be619 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
@@ -212,6 +212,11 @@
performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_PAUSE);
}
+ @Test
+ public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
+ performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_STOP);
+ }
+
private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
// Make sure home activity is visible.
launchHomeActivity();
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
index 070fce1..c912e6e 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
@@ -2,12 +2,16 @@
import static android.server.am.StateLogger.log;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback
+ .ON_MULTI_WINDOW_MODE_CHANGED;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
import android.annotation.Nullable;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.server.am.ActivityManagerTestBase;
@@ -30,6 +34,12 @@
static final String EXTRA_FINISH_IN_ON_RESUME = "finish_in_on_resume";
static final String EXTRA_FINISH_AFTER_RESUME = "finish_after_resume";
+ static final ComponentName CALLBACK_TRACKING_ACTIVITY =
+ getComponentName(CallbackTrackingActivity.class);
+
+ static final ComponentName CONFIG_CHANGE_HANDLING_ACTIVITY =
+ getComponentName(ConfigChangeHandlingActivity.class);
+
final ActivityTestRule mFirstActivityTestRule = new ActivityTestRule(FirstActivity.class,
true /* initialTouchMode */, false /* launchActivity */);
@@ -53,6 +63,10 @@
final ActivityTestRule mSingleTopActivityTestRule = new ActivityTestRule(
SingleTopActivity.class, true /* initialTouchMode */, false /* launchActivity */);
+ final ActivityTestRule mConfigChangeHandlingActivityTestRule = new ActivityTestRule(
+ ConfigChangeHandlingActivity.class, true /* initialTouchMode */,
+ false /* launchActivity */);
+
private final ActivityLifecycleMonitor mLifecycleMonitor = ActivityLifecycleMonitorRegistry
.getInstance();
private static LifecycleLog mLifecycleLog;
@@ -149,6 +163,11 @@
super.onNewIntent(intent);
mLifecycleLog.onActivityCallback(this, ON_NEW_INTENT);
}
+
+ @Override
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
+ mLifecycleLog.onActivityCallback(this, ON_MULTI_WINDOW_MODE_CHANGED);
+ }
}
/**
@@ -195,4 +214,12 @@
}
}
}
+
+ // Config change handling activity
+ public static class ConfigChangeHandlingActivity extends CallbackTrackingActivity {
+ }
+
+ static ComponentName getComponentName(Class<? extends Activity> activity) {
+ return new ComponentName(InstrumentationRegistry.getContext(), activity);
+ }
}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
index da47687..fa9894f 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
@@ -1,8 +1,14 @@
package android.server.am.lifecycle;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.UiDeviceUtils.pressBackButton;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback
+ .ON_MULTI_WINDOW_MODE_CHANGED;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
@@ -11,8 +17,6 @@
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
-import static android.server.am.ActivityManagerState.STATE_STOPPED;
-import static android.server.am.UiDeviceUtils.pressBackButton;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
@@ -598,4 +602,81 @@
LifecycleVerifier.assertSequenceMatchesOneOf(SingleTopActivity.class, getLifecycleLog(),
Arrays.asList(expectedSequence, extraPauseSequence), "newIntent");
}
+
+ @Test
+ public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
+ // Launch a singleTop activity
+ final Activity testActivity =
+ mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+
+ // Wait for the activity to resume
+ waitAndAssertActivityStates(state(testActivity, ON_RESUME));
+ LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+ true /* includeCallbacks */);
+
+ // Enter split screen
+ getLifecycleLog().clear();
+ setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+ // Wait for the activity to pause
+ final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
+ Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
+ ON_POST_CREATE, ON_RESUME, ON_MULTI_WINDOW_MODE_CHANGED, ON_PAUSE);
+ waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
+
+ // Verify that the activity was relaunched and received multi-window mode change
+ LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+ expectedEnterSequence, "moveToSplitScreen");
+
+ // Exit split-screen
+ getLifecycleLog().clear();
+ setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+ // Wait for the activity to resume
+ final List<LifecycleLog.ActivityCallback> expectedExitSequence =
+ Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
+ ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_MULTI_WINDOW_MODE_CHANGED,
+ ON_RESUME);
+ waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
+
+ // Verify that the activity was relaunched and received multi-window mode change
+ LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+ expectedExitSequence, "moveFromSplitScreen");
+ }
+
+ @Test
+ public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
+ // Launch a singleTop activity
+ final Activity testActivity =
+ mConfigChangeHandlingActivityTestRule.launchActivity(new Intent());
+
+ // Wait for the activity to resume
+ waitAndAssertActivityStates(state(testActivity, ON_RESUME));
+ LifecycleVerifier.assertLaunchSequence(ConfigChangeHandlingActivity.class,
+ getLifecycleLog(), true /* includeCallbacks */);
+
+ // Enter split screen
+ getLifecycleLog().clear();
+ setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+ // Wait for the activity to pause
+ waitAndAssertActivityStates(state(testActivity, ON_PAUSE));
+
+ // Verify that the activity was relaunched and received multi-window mode change
+ LifecycleVerifier.assertSequence(ConfigChangeHandlingActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_PAUSE), "moveToSplitScreen");
+
+ // Exit split-screen
+ getLifecycleLog().clear();
+ setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+ // Wait for the activity to resume
+ waitAndAssertActivityStates(state(testActivity, ON_RESUME));
+
+ // Verify that the activity was relaunched and received multi-window mode change
+ LifecycleVerifier.assertSequence(ConfigChangeHandlingActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_RESUME), "moveFromSplitScreen");
+ }
}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
index 3a8705a..da8296c 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
@@ -34,7 +34,8 @@
ON_DESTROY,
ON_ACTIVITY_RESULT,
ON_POST_CREATE,
- ON_NEW_INTENT
+ ON_NEW_INTENT,
+ ON_MULTI_WINDOW_MODE_CHANGED
}
/**
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
index 0809f3e..5a6b0be 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
@@ -399,7 +399,8 @@
}
protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) {
- final int taskId = getActivityTaskId(activityName);
+ mAmWmState.computeState(activityName);
+ final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
.setActivityType(ACTIVITY_TYPE_STANDARD)
@@ -408,7 +409,8 @@
}
protected void moveActivityToStack(ComponentName activityName, int stackId) {
- final int taskId = getActivityTaskId(activityName);
+ mAmWmState.computeState(activityName);
+ final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
executeShellCommand(cmd);
@@ -419,7 +421,8 @@
protected void resizeActivityTask(
ComponentName activityName, int left, int top, int right, int bottom) {
- final int taskId = getActivityTaskId(activityName);
+ mAmWmState.computeState(activityName);
+ final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
final String cmd = "am task resize "
+ taskId + " " + left + " " + top + " " + right + " " + bottom;
executeShellCommand(cmd);
@@ -452,25 +455,6 @@
}
}
- @Deprecated
- protected int getActivityTaskId(final ComponentName activityName) {
- final String windowName = getWindowName(activityName);
- final String output = executeShellCommand(AM_STACK_LIST);
- final Pattern activityPattern = Pattern.compile("(.*) " + windowName + " (.*)");
- for (final String line : output.split("\\n")) {
- final Matcher matcher = activityPattern.matcher(line);
- if (matcher.matches()) {
- for (String word : line.split("\\s+")) {
- if (word.startsWith(TASK_ID_PREFIX)) {
- final String withColon = word.split("=")[1];
- return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
- }
- }
- }
- }
- return -1;
- }
-
protected boolean supportsVrMode() {
return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
index 8708b83..de3235c 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
@@ -62,6 +62,7 @@
import com.android.cts.mockime.MockImeSession;
import org.junit.BeforeClass;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,7 +85,15 @@
}
@BeforeClass
- public static void checkNavigationBar() throws Exception {
+ public static void initializeNavigationBarInfo() throws Exception {
+ // Make sure that NavigationBarInfo is initialized before
+ // EndToEndImeTestBase#showStateInitializeActivity().
+ NavigationBarInfo.getInstance();
+ }
+
+ // TODO(b/37502066): Merge this back to initializeNavigationBarInfo() once b/37502066 is fixed.
+ @Before
+ public void checkNavigationBar() throws Exception {
assumeTrue("This test does not make sense if there is no navigation bar",
NavigationBarInfo.getInstance().hasBottomNavigationBar());
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
index 48710ec..add3237 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
@@ -23,19 +23,15 @@
import android.support.test.InstrumentationRegistry;
import org.junit.Before;
-import org.junit.BeforeClass;
public class EndToEndImeTestBase {
-
- @BeforeClass
- public static void assumeFeatureInputMethod() {
+ @Before
+ public void showStateInitializeActivity() {
+ // TODO(b/37502066): Move this back to @BeforeClass once b/37502066 is fixed.
assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_INPUT_METHODS));
- }
- @Before
- public void showStateInitializeActivity() {
final Intent intent = new Intent()
.setAction(Intent.ACTION_MAIN)
.setClass(InstrumentationRegistry.getTargetContext(), StateInitializeActivity.class)
diff --git a/tests/tests/appwidget/Android.mk b/tests/tests/appwidget/Android.mk
index b6243fc..dea9c46 100644
--- a/tests/tests/appwidget/Android.mk
+++ b/tests/tests/appwidget/Android.mk
@@ -24,7 +24,7 @@
$(call all-java-files-under, common/src)
LOCAL_PACKAGE_NAME := CtsAppWidgetTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_SDK_VERSION := test_current
LOCAL_STATIC_JAVA_LIBRARIES := \
mockito-target-minus-junit4 \
diff --git a/tests/tests/appwidget/AndroidTest.xml b/tests/tests/appwidget/AndroidTest.xml
index cc4fe42..e64123c 100644
--- a/tests/tests/appwidget/AndroidTest.xml
+++ b/tests/tests/appwidget/AndroidTest.xml
@@ -43,6 +43,7 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="hidden-api-checks" value="false" />
<option name="package" value="android.appwidget.cts" />
<option name="runtime-hint" value="9m30s" />
</test>
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
index 5c4f517..08777c1 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -23,7 +23,6 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
@@ -204,7 +203,7 @@
final Bundle secondOptions;
// Create a host and start listening.
- AppWidgetHost host = spy(new AppWidgetHost(getInstrumentation().getTargetContext(), 0));
+ AppWidgetHost host = new AppWidgetHost(getInstrumentation().getTargetContext(), 0);
host.deleteHost();
host.startListening();
@@ -314,8 +313,7 @@
int secondAppWidgetId = 0;
// Create a host and start listening.
- AppWidgetHost host = spy(new AppWidgetHost(
- getInstrumentation().getTargetContext(), 0));
+ AppWidgetHost host = new AppWidgetHost(getInstrumentation().getTargetContext(), 0);
host.deleteHost();
host.startListening();
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index 6df9f8e..7d88ec2 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -54,7 +54,8 @@
ndkaudio \
testng \
truth-prebuilt \
- mockito-target-minus-junit4
+ mockito-target-minus-junit4 \
+ androidx.heifwriter_heifwriter \
LOCAL_JNI_SHARED_LIBRARIES := \
libaudio_jni \
diff --git a/tests/tests/media/res/raw/heifwriter_input.heic b/tests/tests/media/res/raw/heifwriter_input.heic
new file mode 100644
index 0000000..ceaff92
--- /dev/null
+++ b/tests/tests/media/res/raw/heifwriter_input.heic
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java b/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java
index a7ad33b..60f2d21 100644
--- a/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java
+++ b/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java
@@ -24,8 +24,14 @@
import android.media.MediaPlayer;
import android.media.audiofx.AudioEffect;
import android.media.audiofx.DynamicsProcessing;
+import android.media.audiofx.DynamicsProcessing.BandBase;
+import android.media.audiofx.DynamicsProcessing.BandStage;
import android.media.audiofx.DynamicsProcessing.Channel;
import android.media.audiofx.DynamicsProcessing.Eq;
+import android.media.audiofx.DynamicsProcessing.EqBand;
+import android.media.audiofx.DynamicsProcessing.Limiter;
+import android.media.audiofx.DynamicsProcessing.Mbc;
+import android.media.audiofx.DynamicsProcessing.MbcBand;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -35,7 +41,6 @@
private DynamicsProcessing mDP;
private static final int MIN_CHANNEL_COUNT = 1;
- private static final int TEST_CHANNEL = 0;
private static final float EPSILON = 0.00001f;
private static final int DEFAULT_VARIANT =
DynamicsProcessing.VARIANT_FAVOR_FREQUENCY_RESOLUTION;
@@ -49,6 +54,12 @@
private static final float DEFAULT_FRAME_DURATION = 9.5f;
private static final float DEFAULT_INPUT_GAIN = -12.5f;
+ private static final int TEST_CHANNEL_COUNT = 2;
+ private static final float TEST_GAIN1 = 12.1f;
+ private static final float TEST_GAIN2 = -2.8f;
+ private static final int TEST_CHANNEL_INDEX = 0;
+ private static final int TEST_BAND_INDEX = 0;
+
//-----------------------------------------------------------------
// DynamicsProcessing tests:
//----------------------------------
@@ -129,7 +140,7 @@
final int channelCount = mDP.getChannelCount();
assertTrue("unexpected channel count", channelCount >= MIN_CHANNEL_COUNT);
- Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL);
+ Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
final float inputGain = channel.getInputGain();
assertEquals("inputGain is different", DEFAULT_INPUT_GAIN, inputGain, EPSILON);
@@ -145,7 +156,7 @@
try {
createDefaultEffect();
- DynamicsProcessing.Eq eq = mDP.getPreEqByChannelIndex(TEST_CHANNEL);
+ DynamicsProcessing.Eq eq = mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
final boolean inUse = eq.isInUse();
assertEquals("inUse is different", DEFAULT_PREEQ_IN_USE, inUse);
@@ -165,7 +176,7 @@
try {
createDefaultEffect();
- DynamicsProcessing.Mbc mbc = mDP.getMbcByChannelIndex(TEST_CHANNEL);
+ DynamicsProcessing.Mbc mbc = mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
final boolean inUse = mbc.isInUse();
assertEquals("inUse is different", DEFAULT_MBC_IN_USE, inUse);
@@ -184,7 +195,7 @@
try {
createDefaultEffect();
- DynamicsProcessing.Eq eq = mDP.getPostEqByChannelIndex(TEST_CHANNEL);
+ DynamicsProcessing.Eq eq = mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
boolean inUse = eq.isInUse();
assertEquals("inUse is different", DEFAULT_POSTEQ_IN_USE, inUse);
@@ -204,7 +215,7 @@
try {
createDefaultEffect();
- DynamicsProcessing.Limiter limiter = mDP.getLimiterByChannelIndex(TEST_CHANNEL);
+ DynamicsProcessing.Limiter limiter = mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
final boolean inUse = limiter.isInUse();
assertEquals("inUse is different", DEFAULT_LIMITER_IN_USE, inUse);
@@ -214,10 +225,838 @@
}
//-----------------------------------------------------------------
- // 2 - run and change parameters
+ // 2 - config builder tests
//----------------------------------
- //TODO: runtime change of parameters.
+ public void test2_0ConfigBasic() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+ DynamicsProcessing.Config config = getBuilderWithValues().build();
+
+ assertEquals("getVariant is different", DEFAULT_VARIANT,
+ config.getVariant());
+ assertEquals("isPreEqInUse is different", DEFAULT_PREEQ_IN_USE,
+ config.isPreEqInUse());
+ assertEquals("getPreEqBandCount is different", DEFAULT_PREEQ_BAND_COUNT,
+ config.getPreEqBandCount());
+ assertEquals("isMbcInUse is different", DEFAULT_MBC_IN_USE,
+ config.isMbcInUse());
+ assertEquals("getMbcBandCount is different", DEFAULT_MBC_BAND_COUNT,
+ config.getMbcBandCount());
+ assertEquals("isPostEqInUse is different", DEFAULT_POSTEQ_IN_USE,
+ config.isPostEqInUse());
+ assertEquals("getPostEqBandCount is different", DEFAULT_POSTEQ_BAND_COUNT,
+ config.getPostEqBandCount());
+ assertEquals("isLimiterInUse is different", DEFAULT_LIMITER_IN_USE,
+ config.isLimiterInUse());
+ assertEquals("getPreferredFrameDuration is different", DEFAULT_FRAME_DURATION,
+ config.getPreferredFrameDuration());
+ }
+
+ public void test2_1ConfigChannel() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+ DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
+
+ //per channel
+ Channel channel = config.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
+ //change some parameters
+ channel.setInputGain(TEST_GAIN1);
+
+ //get stages from channel
+ Eq preEq = channel.getPreEq();
+ EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
+ preEqBand.setGain(TEST_GAIN1);
+ channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
+
+ Mbc mbc = channel.getMbc();
+ MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
+ mbcBand.setPreGain(TEST_GAIN1);
+ channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
+
+ Eq postEq = channel.getPostEq();
+ EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
+ postEqBand.setGain(TEST_GAIN1);
+ channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
+
+ Limiter limiter = channel.getLimiter();
+ limiter.setPostGain(TEST_GAIN1);
+ channel.setLimiter(limiter);
+
+ config.setAllChannelsTo(channel);
+ for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+ Channel channelTest = new Channel(config.getChannelByChannelIndex(i));
+ assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
+ channelTest.getInputGain(), EPSILON);
+
+ EqBand preEqBandTest = new EqBand(channelTest.getPreEqBand(TEST_BAND_INDEX));
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+ MbcBand mbcBandTest = new MbcBand(channelTest.getMbcBand(TEST_BAND_INDEX));
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+ EqBand postEqBandTest = new EqBand(channelTest.getPostEqBand(TEST_BAND_INDEX));
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+ Limiter limiterTest = new Limiter(channelTest.getLimiter());
+ assertEquals("limiter gain is different in channel " + i,
+ TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
+
+ ///changes per channelIndex
+ channelTest.setInputGain(TEST_GAIN2);
+ preEqBandTest.setGain(TEST_GAIN2);
+ channelTest.setPreEqBand(TEST_BAND_INDEX, preEqBandTest);
+ mbcBandTest.setPreGain(TEST_GAIN2);
+ channelTest.setMbcBand(TEST_BAND_INDEX, mbcBandTest);
+ postEqBandTest.setGain(TEST_GAIN2);
+ channelTest.setPostEqBand(TEST_BAND_INDEX, postEqBandTest);
+ limiterTest.setPostGain(TEST_GAIN2);
+ channelTest.setLimiter(limiterTest);
+ config.setChannelTo(i, channelTest);
+
+ assertEquals("inputGain is different in channel " + i, TEST_GAIN2,
+ config.getInputGainByChannelIndex(i), EPSILON);
+
+ //get by module
+ Eq preEqTest = config.getPreEqByChannelIndex(i);
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, preEqTest.getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+ Mbc mbcTest = config.getMbcByChannelIndex(i);
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, mbcTest.getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+ Eq postEqTest = config.getPostEqByChannelIndex(i);
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, postEqTest.getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+ limiterTest = config.getLimiterByChannelIndex(i);
+ assertEquals("limiter gain is different in channel " + i,
+ TEST_GAIN2, limiterTest.getPostGain(), EPSILON);
+ }
+ }
+
+ public void test2_2ConfigChannel_perStage() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
+
+ //Per Stage
+ config.setInputGainAllChannelsTo(TEST_GAIN1);
+
+ Eq preEq = config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
+ EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
+ preEqBand.setGain(TEST_GAIN1);
+ preEq.setBand(TEST_BAND_INDEX, preEqBand);
+ config.setPreEqAllChannelsTo(preEq);
+
+ Mbc mbc = config.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
+ MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
+ mbcBand.setPreGain(TEST_GAIN1);
+ mbc.setBand(TEST_BAND_INDEX, mbcBand);
+ config.setMbcAllChannelsTo(mbc);
+
+ Eq postEq = config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
+ EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
+ postEqBand.setGain(TEST_GAIN1);
+ postEq.setBand(TEST_BAND_INDEX, postEqBand);
+ config.setPostEqAllChannelsTo(postEq);
+
+ Limiter limiter = config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
+ limiter.setPostGain(TEST_GAIN1);
+ config.setLimiterAllChannelsTo(limiter);
+
+ for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+ Channel channelTest = config.getChannelByChannelIndex(i);
+ assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
+ channelTest.getInputGain(), EPSILON);
+
+ Eq preEqTest = new Eq(config.getPreEqByChannelIndex(i));
+ EqBand preEqBandTest = preEqTest.getBand(TEST_BAND_INDEX);
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+ Mbc mbcTest = new Mbc(config.getMbcByChannelIndex(i));
+ MbcBand mbcBandTest = mbcTest.getBand(TEST_BAND_INDEX);
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+ Eq postEqTest = new Eq(config.getPostEqByChannelIndex(i));
+ EqBand postEqBandTest = postEqTest.getBand(TEST_BAND_INDEX);
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+ Limiter limiterTest = new Limiter(config.getLimiterByChannelIndex(i));
+ assertEquals("limiter gain is different in channel " + i,
+ TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
+
+ //change by Stage
+ preEqBandTest.setGain(TEST_GAIN2);
+ preEqTest.setBand(TEST_BAND_INDEX, preEqBandTest);
+ config.setPreEqByChannelIndex(i, preEqTest);
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, config.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ mbcBandTest.setPreGain(TEST_GAIN2);
+ mbcTest.setBand(TEST_BAND_INDEX, mbcBandTest);
+ config.setMbcByChannelIndex(i, mbcTest);
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+ EPSILON);
+
+ postEqBandTest.setGain(TEST_GAIN2);
+ postEqTest.setBand(TEST_BAND_INDEX, postEqBandTest);
+ config.setPostEqByChannelIndex(i, postEqTest);
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, config.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ limiterTest.setPostGain(TEST_GAIN2);
+ config.setLimiterByChannelIndex(i, limiterTest);
+ assertEquals("limiter gain is different in channel " + i,
+ TEST_GAIN2, config.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+ }
+ }
+
+ public void test2_3ConfigChannel_perBand() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+ DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
+
+ //Per Band
+ EqBand preEqBand = config.getPreEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+ preEqBand.setGain(TEST_GAIN1);
+ config.setPreEqBandAllChannelsTo(TEST_BAND_INDEX, preEqBand);
+
+ MbcBand mbcBand = config.getMbcBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+ mbcBand.setPreGain(TEST_GAIN1);
+ config.setMbcBandAllChannelsTo(TEST_BAND_INDEX, mbcBand);
+
+ EqBand postEqBand = config.getPostEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+ postEqBand.setGain(TEST_GAIN1);
+ config.setPostEqBandAllChannelsTo(TEST_BAND_INDEX, postEqBand);
+
+ for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+
+ EqBand preEqBandTest = new EqBand(config.getPreEqBandByChannelIndex(i,
+ TEST_BAND_INDEX));
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+ MbcBand mbcBandTest = new MbcBand(config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX));
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+ EqBand postEqBandTest = new EqBand(config.getPostEqBandByChannelIndex(i,
+ TEST_BAND_INDEX));
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+ //change per Band
+ preEqBandTest.setGain(TEST_GAIN2);
+ config.setPreEqBandByChannelIndex(i, TEST_BAND_INDEX, preEqBandTest);
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, config.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ mbcBandTest.setPreGain(TEST_GAIN2);
+ config.setMbcBandByChannelIndex(i, TEST_BAND_INDEX, mbcBandTest);
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+ EPSILON);
+
+ postEqBandTest.setGain(TEST_GAIN2);
+ config.setPostEqBandByChannelIndex(i, TEST_BAND_INDEX, postEqBandTest);
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, config.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+ }
+ }
+
+ public void test2_4Channel_perStage() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+ DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+
+ Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+ channel.setInputGain(TEST_GAIN1);
+ assertEquals("channel gain is different", TEST_GAIN1, channel.getInputGain(), EPSILON);
+
+ //set by stage
+ Eq preEq = new Eq(channel.getPreEq());
+ EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+ preEqBand.setGain(TEST_GAIN1);
+ preEq.setBand(TEST_BAND_INDEX, preEqBand);
+ channel.setPreEq(preEq);
+ assertEquals("preEQBand gain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, channel.getPreEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+ preEqBand.setGain(TEST_GAIN2);
+ preEq.setBand(TEST_BAND_INDEX, preEqBand);
+ channel.setPreEq(preEq);
+ assertEquals("preEQBand gain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, channel.getPreEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+ Mbc mbc = new Mbc(channel.getMbc());
+ MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+ mbcBand.setPreGain(TEST_GAIN1);
+ mbc.setBand(TEST_BAND_INDEX, mbcBand);
+ channel.setMbc(mbc);
+ assertEquals("mbcBand preGain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, channel.getMbc().getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+ mbcBand.setPreGain(TEST_GAIN2);
+ mbc.setBand(TEST_BAND_INDEX, mbcBand);
+ channel.setMbc(mbc);
+ assertEquals("mbcBand preGain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, channel.getMbc().getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+ Eq postEq = new Eq(channel.getPostEq());
+ EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+ postEqBand.setGain(TEST_GAIN1);
+ postEq.setBand(TEST_BAND_INDEX, postEqBand);
+ channel.setPostEq(postEq);
+ assertEquals("postEqBand gain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, channel.getPostEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+ postEqBand.setGain(TEST_GAIN2);
+ postEq.setBand(TEST_BAND_INDEX, postEqBand);
+ channel.setPostEq(postEq);
+ assertEquals("postEQBand gain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, channel.getPostEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+ Limiter limiter = new Limiter(channel.getLimiter());
+ limiter.setPostGain(TEST_GAIN1);
+ channel.setLimiter(limiter);
+ assertEquals("limiter gain is different",
+ TEST_GAIN1, channel.getLimiter().getPostGain(), EPSILON);
+ limiter.setPostGain(TEST_GAIN2);
+ channel.setLimiter(limiter);
+ assertEquals("limiter gain is different",
+ TEST_GAIN2, channel.getLimiter().getPostGain(), EPSILON);
+
+ }
+
+ public void test2_5Channel_perBand() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+ DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+
+ Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+ channel.setInputGain(TEST_GAIN1);
+ assertEquals("channel gain is different", TEST_GAIN1, channel.getInputGain(), EPSILON);
+
+ //set by band
+ EqBand preEqBand = new EqBand(channel.getPreEqBand(TEST_BAND_INDEX));
+ preEqBand.setGain(TEST_GAIN1);
+ channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
+ assertEquals("preEQBand gain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, channel.getPreEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+ preEqBand.setGain(TEST_GAIN2);
+ channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
+ assertEquals("preEQBand gain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, channel.getPreEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+ MbcBand mbcBand = new MbcBand(channel.getMbcBand(TEST_BAND_INDEX));
+ mbcBand.setPreGain(TEST_GAIN1);
+ channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
+ assertEquals("mbcBand preGain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, channel.getMbcBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+ mbcBand.setPreGain(TEST_GAIN2);
+ channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
+ assertEquals("mbcBand preGain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, channel.getMbcBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+ EqBand postEqBand = new EqBand(channel.getPostEqBand(TEST_BAND_INDEX));
+ postEqBand.setGain(TEST_GAIN1);
+ channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
+ assertEquals("postEqBand gain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, channel.getPostEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+ postEqBand.setGain(TEST_GAIN2);
+ channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
+ assertEquals("postEqBand gain is different in band "+ TEST_BAND_INDEX,
+ TEST_GAIN2, channel.getPostEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+ }
+
+ public void test2_6Eq() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+ final boolean inUse = true;
+ final boolean enabled = true;
+ final int bandCount = 3;
+
+ Eq eq = new Eq(inUse, enabled, bandCount);
+ assertEquals("eq inUse is different", inUse, eq.isInUse());
+ assertEquals("eq enabled is different", enabled, eq.isEnabled());
+ assertEquals("eq bandCount is different", bandCount, eq.getBandCount());
+
+ //changes
+ eq.setEnabled(!enabled);
+ assertEquals("eq enabled is different", !enabled, eq.isEnabled());
+
+ //bands
+ for (int i = 0; i < bandCount; i++) {
+ final float frequency = (i + 1) * 100.3f;
+ final float gain = (i+1) * 10.1f;
+ EqBand eqBand = new EqBand(eq.getBand(i));
+
+ eqBand.setEnabled(enabled);
+ eqBand.setCutoffFrequency(frequency);
+ eqBand.setGain(gain);
+
+ eq.setBand(i, eqBand);
+
+ //compare
+ assertEquals("eq enabled is different in band " + i, enabled,
+ eq.getBand(i).isEnabled());
+ assertEquals("eq cutoffFrequency is different in band "+ i,
+ frequency, eq.getBand(i).getCutoffFrequency(), EPSILON);
+ assertEquals("eq eqBand gain is different in band "+ i,
+ gain, eq.getBand(i).getGain(), EPSILON);
+ }
+ }
+
+ public void test2_7Mbc() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ final boolean inUse = true;
+ final boolean enabled = true;
+ final int bandCount = 3;
+
+ Mbc mbc = new Mbc(inUse, enabled, bandCount);
+ assertEquals("mbc inUse is different", inUse, mbc.isInUse());
+ assertEquals("mbc enabled is different", enabled, mbc.isEnabled());
+ assertEquals("mbc bandCount is different", bandCount, mbc.getBandCount());
+
+ //changes
+ mbc.setEnabled(!enabled);
+ assertEquals("enabled is different", !enabled, mbc.isEnabled());
+
+ //bands
+ for (int i = 0; i < bandCount; i++) {
+ int index = i+1;
+ final float frequency = index * 100.3f;
+ final float attackTime = index * 3.2f;
+ final float releaseTime = 2 * attackTime;
+ final float ratio = index * 1.2f;
+ final float threshold = index * (-12.8f);
+ final float kneeWidth = index * 0.3f;
+ final float noiseGateThreshold = index * (-20.1f);
+ final float expanderRatio = index * 1.1f;
+ final float preGain = index * 10.1f;
+ final float postGain = index * (-0.2f);
+ MbcBand mbcBand = new MbcBand(mbc.getBand(i));
+
+ mbcBand.setEnabled(enabled);
+ mbcBand.setCutoffFrequency(frequency);
+ mbcBand.setAttackTime(attackTime);
+ mbcBand.setReleaseTime(releaseTime);
+ mbcBand.setRatio(ratio);
+ mbcBand.setThreshold(threshold);
+ mbcBand.setKneeWidth(kneeWidth);
+ mbcBand.setNoiseGateThreshold(noiseGateThreshold);
+ mbcBand.setExpanderRatio(expanderRatio);
+ mbcBand.setPreGain(preGain);
+ mbcBand.setPostGain(postGain);
+
+ mbc.setBand(i, mbcBand);
+
+ //compare
+ assertEquals("mbc enabled is different", enabled, mbc.getBand(i).isEnabled());
+ assertEquals("mbc cutoffFrequency is different in band "+ i,
+ frequency, mbc.getBand(i).getCutoffFrequency(), EPSILON);
+ assertEquals("mbc attackTime is different in band "+ i,
+ attackTime, mbc.getBand(i).getAttackTime(), EPSILON);
+ assertEquals("mbc releaseTime is different in band "+ i,
+ releaseTime, mbc.getBand(i).getReleaseTime(), EPSILON);
+ assertEquals("mbc ratio is different in band "+ i,
+ ratio, mbc.getBand(i).getRatio(), EPSILON);
+ assertEquals("mbc threshold is different in band "+ i,
+ threshold, mbc.getBand(i).getThreshold(), EPSILON);
+ assertEquals("mbc kneeWidth is different in band "+ i,
+ kneeWidth, mbc.getBand(i).getKneeWidth(), EPSILON);
+ assertEquals("mbc noiseGateThreshold is different in band "+ i,
+ noiseGateThreshold, mbc.getBand(i).getNoiseGateThreshold(), EPSILON);
+ assertEquals("mbc expanderRatio is different in band "+ i,
+ expanderRatio, mbc.getBand(i).getExpanderRatio(), EPSILON);
+ assertEquals("mbc preGain is different in band "+ i,
+ preGain, mbc.getBand(i).getPreGain(), EPSILON);
+ assertEquals("mbc postGain is different in band "+ i,
+ postGain, mbc.getBand(i).getPostGain(), EPSILON);
+ }
+ }
+
+ public void test2_8Limiter() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ final boolean inUse = true;
+ final boolean enabled = true;
+ final int linkGroup = 4;
+ final float attackTime = 3.2f;
+ final float releaseTime = 2 * attackTime;
+ final float ratio = 1.2f;
+ final float threshold = (-12.8f);
+ final float postGain = (-0.2f);
+
+ Limiter limiter = new Limiter(inUse, enabled, linkGroup, attackTime, releaseTime, ratio,
+ threshold, postGain);
+ assertEquals("limiter inUse is different", inUse, limiter.isInUse());
+ assertEquals("limiter enabled is different", enabled, limiter.isEnabled());
+ assertEquals("limiter linkGroup is different", linkGroup, limiter.getLinkGroup());
+
+ //defaults
+ assertEquals("limiter attackTime is different",
+ attackTime, limiter.getAttackTime(), EPSILON);
+ assertEquals("limiter releaseTime is different",
+ releaseTime, limiter.getReleaseTime(), EPSILON);
+ assertEquals("limiter ratio is different",
+ ratio, limiter.getRatio(), EPSILON);
+ assertEquals("limiter threshold is different",
+ threshold, limiter.getThreshold(), EPSILON);
+ assertEquals("limiter postGain is different",
+ postGain, limiter.getPostGain(), EPSILON);
+
+ //changes
+ final boolean newEnabled = !enabled;
+ final int newLinkGroup = 7;
+ final float newAttackTime = attackTime + 10;
+ final float newReleaseTime = releaseTime + 10;
+ final float newRatio = ratio + 2f;
+ final float newThreshold = threshold -20f;
+ final float newPostGain = postGain + 3f;
+
+ limiter.setEnabled(newEnabled);
+ limiter.setLinkGroup(newLinkGroup);
+ limiter.setAttackTime(newAttackTime);
+ limiter.setReleaseTime(newReleaseTime);
+ limiter.setRatio(newRatio);
+ limiter.setThreshold(newThreshold);
+ limiter.setPostGain(newPostGain);
+
+ assertEquals("limiter enabled is different", newEnabled, limiter.isEnabled());
+ assertEquals("limiter linkGroup is different", newLinkGroup, limiter.getLinkGroup());
+ assertEquals("limiter attackTime is different",
+ newAttackTime, limiter.getAttackTime(), EPSILON);
+ assertEquals("limiter releaseTime is different",
+ newReleaseTime, limiter.getReleaseTime(), EPSILON);
+ assertEquals("limiter ratio is different",
+ newRatio, limiter.getRatio(), EPSILON);
+ assertEquals("limiter threshold is different",
+ newThreshold, limiter.getThreshold(), EPSILON);
+ assertEquals("limiter postGain is different",
+ newPostGain, limiter.getPostGain(), EPSILON);
+ }
+
+ public void test2_9BandStage() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ final boolean inUse = true;
+ final boolean enabled = true;
+ final int bandCount = 3;
+
+ BandStage bandStage = new BandStage(inUse, enabled, bandCount);
+ assertEquals("bandStage inUse is different", inUse, bandStage.isInUse());
+ assertEquals("bandStage enabled is different", enabled, bandStage.isEnabled());
+ assertEquals("bandStage bandCount is different", bandCount, bandStage.getBandCount());
+
+ //change
+ bandStage.setEnabled(!enabled);
+ assertEquals("bandStage enabled is different", !enabled, bandStage.isEnabled());
+ }
+
+ public void test2_10Stage() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ final boolean inUse = true;
+ final boolean enabled = true;
+ final int bandCount = 3;
+
+ DynamicsProcessing.Stage stage = new DynamicsProcessing.Stage(inUse, enabled);
+ assertEquals("stage inUse is different", inUse, stage.isInUse());
+ assertEquals("stage enabled is different", enabled, stage.isEnabled());
+
+ //change
+ stage.setEnabled(!enabled);
+ assertEquals("stage enabled is different", !enabled, stage.isEnabled());
+ }
+
+ public void test2_11BandBase() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ final boolean enabled = true;
+ final float frequency = 100.3f;
+
+ BandBase bandBase = new BandBase(enabled, frequency);
+
+ assertEquals("bandBase enabled is different", enabled, bandBase.isEnabled());
+ assertEquals("bandBase cutoffFrequency is different",
+ frequency, bandBase.getCutoffFrequency(), EPSILON);
+
+ //change
+ final float newFrequency = frequency + 10f;
+ bandBase.setEnabled(!enabled);
+ bandBase.setCutoffFrequency(newFrequency);
+ assertEquals("bandBase enabled is different", !enabled, bandBase.isEnabled());
+ assertEquals("bandBase cutoffFrequency is different",
+ newFrequency, bandBase.getCutoffFrequency(), EPSILON);
+ }
+ //-----------------------------------------------------------------
+ // 3 - Builder
+ //----------------------------------
+
+ public void test3_0Builder_stagesAllChannels() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+ DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+ //get Stages, apply all channels
+ Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+ EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+ preEqBand.setGain(TEST_GAIN1);
+ preEq.setBand(TEST_BAND_INDEX, preEqBand);
+ builder.setPreEqAllChannelsTo(preEq);
+
+ Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+ MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+ mbcBand.setPreGain(TEST_GAIN1);
+ mbc.setBand(TEST_BAND_INDEX, mbcBand);
+ builder.setMbcAllChannelsTo(mbc);
+
+ Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+ EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+ postEqBand.setGain(TEST_GAIN1);
+ postEq.setBand(TEST_BAND_INDEX, postEqBand);
+ builder.setPostEqAllChannelsTo(postEq);
+
+ Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+ limiter.setPostGain(TEST_GAIN1);
+ builder.setLimiterAllChannelsTo(limiter);
+
+ //build and compare
+ DynamicsProcessing.Config newConfig = builder.build();
+ for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+ EPSILON);
+
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ assertEquals("limiter gain is different in channel " + i,
+ TEST_GAIN1, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+ }
+ }
+
+ public void test3_1Builder_stagesByChannelIndex() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+ DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+ Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+ EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+
+ Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+ MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+
+ Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+ EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+
+ Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+
+ //get Stages, apply per channel
+ for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+ float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+
+ preEqBand.setGain(gain);
+ preEq.setBand(TEST_BAND_INDEX, preEqBand);
+ builder.setPreEqByChannelIndex(i, preEq);
+
+ mbcBand.setPreGain(gain);
+ mbc.setBand(TEST_BAND_INDEX, mbcBand);
+ builder.setMbcByChannelIndex(i, mbc);
+
+ postEqBand.setGain(gain);
+ postEq.setBand(TEST_BAND_INDEX, postEqBand);
+ builder.setPostEqByChannelIndex(i,postEq);
+
+ limiter.setPostGain(gain);
+ builder.setLimiterByChannelIndex(i, limiter);
+ }
+ //build and compare
+ DynamicsProcessing.Config newConfig = builder.build();
+ for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+ float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ expectedGain,
+ newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ expectedGain,
+ newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+ EPSILON);
+
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ expectedGain,
+ newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ assertEquals("limiter gain is different in channel " + i,
+ expectedGain, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+ }
+ }
+
+ public void test3_2Builder_setAllChannelsTo() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+ DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+ Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+ //get Stages, apply all channels
+ Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+ EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+ preEqBand.setGain(TEST_GAIN1);
+ preEq.setBand(TEST_BAND_INDEX, preEqBand);
+ channel.setPreEq(preEq);
+
+ Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+ MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+ mbcBand.setPreGain(TEST_GAIN1);
+ mbc.setBand(TEST_BAND_INDEX, mbcBand);
+ channel.setMbc(mbc);
+
+ Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+ EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+ postEqBand.setGain(TEST_GAIN1);
+ postEq.setBand(TEST_BAND_INDEX, postEqBand);
+ channel.setPostEq(postEq);
+
+ Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+ limiter.setPostGain(TEST_GAIN1);
+ channel.setLimiter(limiter);
+
+ builder.setAllChannelsTo(channel);
+ //build and compare
+ DynamicsProcessing.Config newConfig = builder.build();
+ for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+ EPSILON);
+
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ TEST_GAIN1, newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ assertEquals("limiter gain is different in channel " + i,
+ TEST_GAIN1, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+ }
+ }
+
+ public void test3_3Builder_setChannelTo() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+ DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+ Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+ Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+ EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+
+ Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+ MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+
+ Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+ EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+
+ Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+
+ //get Stages, apply per channel
+ for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+ float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+
+ preEqBand.setGain(gain);
+ preEq.setBand(TEST_BAND_INDEX, preEqBand);
+ channel.setPreEq(preEq);
+
+ mbcBand.setPreGain(gain);
+ mbc.setBand(TEST_BAND_INDEX, mbcBand);
+ channel.setMbc(mbc);
+
+ postEqBand.setGain(gain);
+ postEq.setBand(TEST_BAND_INDEX, postEqBand);
+ channel.setPostEq(postEq);
+
+ limiter.setPostGain(gain);
+ channel.setLimiter(limiter);
+
+ builder.setChannelTo(i, channel);
+ }
+ //build and compare
+ DynamicsProcessing.Config newConfig = builder.build();
+ for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+ float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+ assertEquals("preEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ expectedGain,
+ newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ assertEquals("mbcBand preGain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ expectedGain,
+ newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+ EPSILON);
+
+ assertEquals("postEQBand gain is different in channel " + i + " band "+ TEST_BAND_INDEX,
+ expectedGain,
+ newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+ EPSILON);
+
+ assertEquals("limiter gain is different in channel " + i,
+ expectedGain, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+ }
+ }
//-----------------------------------------------------------------
// private methods
//----------------------------------
@@ -260,11 +1099,11 @@
createDynamicsProcessingWithConfig(session, config);
}
- private DynamicsProcessing.Config.Builder getBuilder() {
+ private DynamicsProcessing.Config.Builder getBuilder(int channelCount) {
//simple config
DynamicsProcessing.Config.Builder builder = new DynamicsProcessing.Config.Builder(
DEFAULT_VARIANT /* variant */,
- MIN_CHANNEL_COUNT /* channels */,
+ channelCount/* channels */,
DEFAULT_PREEQ_IN_USE /*enable preEQ*/,
DEFAULT_PREEQ_BAND_COUNT /*preEq bands*/,
DEFAULT_MBC_IN_USE /*enable mbc*/,
@@ -276,13 +1115,18 @@
return builder;
}
- private DynamicsProcessing.Config.Builder getBuilderWithValues() {
+ private DynamicsProcessing.Config.Builder getBuilderWithValues(int channelCount) {
//simple config
- DynamicsProcessing.Config.Builder builder = getBuilder();
+ DynamicsProcessing.Config.Builder builder = getBuilder(channelCount);
//Set Defaults
builder.setPreferredFrameDuration(DEFAULT_FRAME_DURATION);
builder.setInputGainAllChannelsTo(DEFAULT_INPUT_GAIN);
return builder;
}
+
+ private DynamicsProcessing.Config.Builder getBuilderWithValues() {
+ return getBuilderWithValues(MIN_CHANNEL_COUNT);
+ }
+
}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
new file mode 100644
index 0000000..08a6527
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.graphics.Bitmap;
+import android.graphics.ImageFormat;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.opengl.GLES20;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_BITMAP;
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_BUFFER;
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_SURFACE;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.heifwriter.HeifWriter;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+public class HeifWriterTest extends AndroidTestCase {
+ private static final String TAG = HeifWriterTest.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private static final boolean DUMP_YUV_INPUT = false;
+
+ private static byte[][] TEST_COLORS = {
+ {(byte) 255, (byte) 0, (byte) 0},
+ {(byte) 255, (byte) 0, (byte) 255},
+ {(byte) 255, (byte) 255, (byte) 255},
+ {(byte) 255, (byte) 255, (byte) 0},
+ };
+
+ private static final String HEIFWRITER_INPUT = "heifwriter_input.heic";
+ private static final int[] IMAGE_RESOURCES = new int[] {
+ R.raw.heifwriter_input
+ };
+ private static final String[] IMAGE_FILENAMES = new String[] {
+ HEIFWRITER_INPUT
+ };
+ private static final String OUTPUT_FILENAME = "output.heic";
+
+ private InputSurface mInputEglSurface;
+ private Handler mHandler;
+ private int mInputIndex;
+
+ @Override
+ public void setUp() throws Exception {
+ for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+ String outputPath = new File(Environment.getExternalStorageDirectory(),
+ IMAGE_FILENAMES[i]).getAbsolutePath();
+
+ InputStream inputStream = null;
+ FileOutputStream outputStream = null;
+ try {
+ inputStream = getContext().getResources().openRawResource(IMAGE_RESOURCES[i]);
+ outputStream = new FileOutputStream(outputPath);
+ copy(inputStream, outputStream);
+ } finally {
+ closeQuietly(inputStream);
+ closeQuietly(outputStream);
+ }
+ }
+
+ HandlerThread handlerThread = new HandlerThread(
+ "HeifEncoderThread", Process.THREAD_PRIORITY_FOREGROUND);
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+ String imageFilePath =
+ new File(Environment.getExternalStorageDirectory(), IMAGE_FILENAMES[i])
+ .getAbsolutePath();
+ File imageFile = new File(imageFilePath);
+ if (imageFile.exists()) {
+ imageFile.delete();
+ }
+ }
+ }
+
+ public void testInputBuffer_NoGrid_NoHandler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, false);
+ doTestForVariousNumberImages(builder);
+ }
+
+ public void testInputBuffer_Grid_NoHandler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, false);
+ doTestForVariousNumberImages(builder);
+ }
+
+ public void testInputBuffer_NoGrid_Handler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, true);
+ doTestForVariousNumberImages(builder);
+ }
+
+ public void testInputBuffer_Grid_Handler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, true);
+ doTestForVariousNumberImages(builder);
+ }
+
+ public void testInputSurface_NoGrid_NoHandler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, false);
+ doTestForVariousNumberImages(builder);
+ }
+
+ public void testInputSurface_Grid_NoHandler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, false);
+ doTestForVariousNumberImages(builder);
+ }
+
+ public void testInputSurface_NoGrid_Handler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, true);
+ doTestForVariousNumberImages(builder);
+ }
+
+ public void testInputSurface_Grid_Handler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, true);
+ doTestForVariousNumberImages(builder);
+ }
+
+ public void testInputBitmap_NoGrid_NoHandler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, false);
+ for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+ String inputPath = new File(Environment.getExternalStorageDirectory(),
+ IMAGE_FILENAMES[i]).getAbsolutePath();
+ doTestForVariousNumberImages(builder.setInputPath(inputPath));
+ }
+ }
+
+ public void testInputBitmap_Grid_NoHandler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, false);
+ for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+ String inputPath = new File(Environment.getExternalStorageDirectory(),
+ IMAGE_FILENAMES[i]).getAbsolutePath();
+ doTestForVariousNumberImages(builder.setInputPath(inputPath));
+ }
+ }
+
+ public void testInputBitmap_NoGrid_Handler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, true);
+ for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+ String inputPath = new File(Environment.getExternalStorageDirectory(),
+ IMAGE_FILENAMES[i]).getAbsolutePath();
+ doTestForVariousNumberImages(builder.setInputPath(inputPath));
+ }
+ }
+
+ public void testInputBitmap_Grid_Handler() throws Throwable {
+ TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, true);
+ for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+ String inputPath = new File(Environment.getExternalStorageDirectory(),
+ IMAGE_FILENAMES[i]).getAbsolutePath();
+ doTestForVariousNumberImages(builder.setInputPath(inputPath));
+ }
+ }
+
+ private static boolean canEncodeHeic() {
+ return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_HEVC)
+ || MediaUtils.hasEncoder(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC);
+ }
+
+ private void doTestForVariousNumberImages(TestConfig.Builder builder) throws Exception {
+ if (!canEncodeHeic()) {
+ MediaUtils.skipTest("heic encoding is not supported on this device");
+ return;
+ }
+
+ builder.setNumImages(4);
+ doTest(builder.setRotation(270).build());
+ doTest(builder.setRotation(180).build());
+ doTest(builder.setRotation(90).build());
+ doTest(builder.setRotation(0).build());
+ doTest(builder.setNumImages(1).build());
+ doTest(builder.setNumImages(8).build());
+ }
+
+ private void closeQuietly(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ private int copy(InputStream in, OutputStream out) throws IOException {
+ int total = 0;
+ byte[] buffer = new byte[8192];
+ int c;
+ while ((c = in.read(buffer)) != -1) {
+ total += c;
+ out.write(buffer, 0, c);
+ }
+ return total;
+ }
+
+ private static class TestConfig {
+ final int mInputMode;
+ final boolean mUseGrid;
+ final boolean mUseHandler;
+ final int mMaxNumImages;
+ final int mNumImages;
+ final int mWidth;
+ final int mHeight;
+ final int mRotation;
+ final int mQuality;
+ final String mInputPath;
+ final String mOutputPath;
+ final Bitmap[] mBitmaps;
+
+ TestConfig(int inputMode, boolean useGrid, boolean useHandler,
+ int maxNumImages, int numImages, int width, int height,
+ int rotation, int quality,
+ String inputPath, String outputPath, Bitmap[] bitmaps) {
+ mInputMode = inputMode;
+ mUseGrid = useGrid;
+ mUseHandler = useHandler;
+ mMaxNumImages = maxNumImages;
+ mNumImages = numImages;
+ mWidth = width;
+ mHeight = height;
+ mRotation = rotation;
+ mQuality = quality;
+ mInputPath = inputPath;
+ mOutputPath = outputPath;
+ mBitmaps = bitmaps;
+ }
+
+ static class Builder {
+ final int mInputMode;
+ final boolean mUseGrid;
+ final boolean mUseHandler;
+ int mMaxNumImages;
+ int mNumImages;
+ int mWidth;
+ int mHeight;
+ int mRotation;
+ final int mQuality;
+ String mInputPath;
+ final String mOutputPath;
+ Bitmap[] mBitmaps;
+ boolean mNumImagesSetExplicitly;
+
+
+ Builder(int inputMode, boolean useGrids, boolean useHandler) {
+ mInputMode = inputMode;
+ mUseGrid = useGrids;
+ mUseHandler = useHandler;
+ mMaxNumImages = mNumImages = 4;
+ mWidth = 1920;
+ mHeight = 1080;
+ mRotation = 0;
+ mQuality = 100;
+ mOutputPath = new File(Environment.getExternalStorageDirectory(),
+ OUTPUT_FILENAME).getAbsolutePath();
+ }
+
+ Builder setInputPath(String inputPath) {
+ mInputPath = (mInputMode == INPUT_MODE_BITMAP) ? inputPath : null;
+ return this;
+ }
+
+ Builder setNumImages(int numImages) {
+ mNumImagesSetExplicitly = true;
+ mNumImages = numImages;
+ return this;
+ }
+
+ Builder setRotation(int rotation) {
+ mRotation = rotation;
+ return this;
+ }
+
+ private void loadBitmapInputs() {
+ if (mInputMode != INPUT_MODE_BITMAP) {
+ return;
+ }
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(mInputPath);
+ String hasImage = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
+ if (!"yes".equals(hasImage)) {
+ throw new IllegalArgumentException("no bitmap found!");
+ }
+ mMaxNumImages = Math.min(mMaxNumImages, Integer.parseInt(retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
+ if (!mNumImagesSetExplicitly) {
+ mNumImages = mMaxNumImages;
+ }
+ mBitmaps = new Bitmap[mMaxNumImages];
+ for (int i = 0; i < mBitmaps.length; i++) {
+ mBitmaps[i] = retriever.getImageAtIndex(i);
+ }
+ mWidth = mBitmaps[0].getWidth();
+ mHeight = mBitmaps[0].getHeight();
+ retriever.release();
+ }
+
+ private void cleanupStaleOutputs() {
+ File outputFile = new File(mOutputPath);
+ if (outputFile.exists()) {
+ outputFile.delete();
+ }
+ }
+
+ TestConfig build() {
+ cleanupStaleOutputs();
+ loadBitmapInputs();
+
+ return new TestConfig(mInputMode, mUseGrid, mUseHandler, mMaxNumImages, mNumImages,
+ mWidth, mHeight, mRotation, mQuality, mInputPath, mOutputPath, mBitmaps);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "TestConfig"
+ + ": mInputMode " + mInputMode
+ + ", mUseGrid " + mUseGrid
+ + ", mUseHandler " + mUseHandler
+ + ", mMaxNumImages " + mMaxNumImages
+ + ", mNumImages " + mNumImages
+ + ", mWidth " + mWidth
+ + ", mHeight " + mHeight
+ + ", mRotation " + mRotation
+ + ", mQuality " + mQuality
+ + ", mInputPath " + mInputPath
+ + ", mOutputPath " + mOutputPath;
+ }
+ }
+
+ private void doTest(TestConfig config) throws Exception {
+ int width = config.mWidth;
+ int height = config.mHeight;
+ int numImages = config.mNumImages;
+
+ mInputIndex = 0;
+ HeifWriter heifWriter = null;
+ FileInputStream inputStream = null;
+ FileOutputStream outputStream = null;
+ try {
+ if (DEBUG) Log.d(TAG, "started: " + config);
+
+ heifWriter = new HeifWriter.Builder(
+ config.mOutputPath, width, height, config.mInputMode)
+ .setRotation(config.mRotation)
+ .setGridEnabled(config.mUseGrid)
+ .setMaxImages(config.mMaxNumImages)
+ .setQuality(config.mQuality)
+ .setPrimaryIndex(config.mMaxNumImages - 1)
+ .setHandler(config.mUseHandler ? mHandler : null)
+ .build();
+
+ if (config.mInputMode == INPUT_MODE_SURFACE) {
+ mInputEglSurface = new InputSurface(heifWriter.getInputSurface());
+ }
+
+ heifWriter.start();
+
+ if (config.mInputMode == INPUT_MODE_BUFFER) {
+ byte[] data = new byte[width * height * 3 / 2];
+
+ if (config.mInputPath != null) {
+ inputStream = new FileInputStream(config.mInputPath);
+ }
+
+ if (DUMP_YUV_INPUT) {
+ File outputFile = new File("/sdcard/input.yuv");
+ outputFile.createNewFile();
+ outputStream = new FileOutputStream(outputFile);
+ }
+
+ for (int i = 0; i < numImages; i++) {
+ if (DEBUG) Log.d(TAG, "fillYuvBuffer: " + i);
+ fillYuvBuffer(i, data, width, height, inputStream);
+ if (DUMP_YUV_INPUT) {
+ Log.d(TAG, "@@@ dumping input YUV");
+ outputStream.write(data);
+ }
+ heifWriter.addYuvBuffer(ImageFormat.YUV_420_888, data);
+ }
+ } else if (config.mInputMode == INPUT_MODE_SURFACE) {
+ // The input surface is a surface texture using single buffer mode, draws will be
+ // blocked until onFrameAvailable is done with the buffer, which is dependant on
+ // how fast MediaCodec processes them, which is further dependent on how fast the
+ // MediaCodec callbacks are handled. We can't put draws on the same looper that
+ // handles MediaCodec callback, it will cause deadlock.
+ for (int i = 0; i < numImages; i++) {
+ if (DEBUG) Log.d(TAG, "drawFrame: " + i);
+ drawFrame(width, height);
+ }
+ heifWriter.setInputEndOfStreamTimestamp(
+ 1000 * computePresentationTime(numImages - 1));
+ } else if (config.mInputMode == INPUT_MODE_BITMAP) {
+ Bitmap[] bitmaps = config.mBitmaps;
+ for (int i = 0; i < Math.min(bitmaps.length, numImages); i++) {
+ if (DEBUG) Log.d(TAG, "addBitmap: " + i);
+ heifWriter.addBitmap(bitmaps[i]);
+ bitmaps[i].recycle();
+ }
+ }
+
+ heifWriter.stop(3000);
+ verifyResult(config.mOutputPath, width, height, config.mRotation, config.mUseGrid,
+ Math.min(numImages, config.mMaxNumImages));
+ if (DEBUG) Log.d(TAG, "finished: PASS");
+ } finally {
+ try {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException e) {}
+
+ if (heifWriter != null) {
+ heifWriter.close();
+ heifWriter = null;
+ }
+ if (mInputEglSurface != null) {
+ // This also releases the surface from encoder.
+ mInputEglSurface.release();
+ mInputEglSurface = null;
+ }
+ }
+ }
+
+ private long computePresentationTime(int frameIndex) {
+ return 132 + (long)frameIndex * 1000000;
+ }
+
+ private void fillYuvBuffer(int frameIndex, @NonNull byte[] data, int width, int height,
+ @Nullable FileInputStream inputStream) throws IOException {
+ if (inputStream != null) {
+ inputStream.read(data);
+ } else {
+ byte[] color = TEST_COLORS[frameIndex % TEST_COLORS.length];
+ int sizeY = width * height;
+ Arrays.fill(data, 0, sizeY, color[0]);
+ Arrays.fill(data, sizeY, sizeY * 5 / 4, color[1]);
+ Arrays.fill(data, sizeY * 5 / 4, sizeY * 3 / 2, color[2]);
+ }
+ }
+
+ private void drawFrame(int width, int height) {
+ mInputEglSurface.makeCurrent();
+ generateSurfaceFrame(mInputIndex, width, height);
+ mInputEglSurface.setPresentationTime(1000 * computePresentationTime(mInputIndex));
+ mInputEglSurface.swapBuffers();
+ mInputIndex++;
+ }
+
+ private void generateSurfaceFrame(int frameIndex, int width, int height) {
+ frameIndex %= 4;
+
+ GLES20.glViewport(0, 0, width, height);
+ GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+ GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+
+ int startX, startY;
+ int borderWidth = 16;
+ for (int i = 0; i < 7; i++) {
+ startX = (width - borderWidth * 2) * i / 7 + borderWidth;
+ GLES20.glScissor(startX, borderWidth,
+ (width - borderWidth * 2) / 7, height - borderWidth * 2);
+ GLES20.glClearColor(((7 - i) & 0x4) * 0.16f,
+ ((7 - i) & 0x2) * 0.32f,
+ ((7 - i) & 0x1) * 0.64f,
+ 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ }
+
+ startX = (width / 6) + (width / 6) * frameIndex;
+ startY = height / 4;
+ GLES20.glScissor(startX, startY, width / 6, height / 3);
+ GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ GLES20.glScissor(startX + borderWidth, startY + borderWidth,
+ width / 6 - borderWidth * 2, height / 3 - borderWidth * 2);
+ GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ }
+
+ private void verifyResult(
+ String filename, int width, int height, int rotation, boolean useGrid, int numImages)
+ throws Exception {
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(filename);
+ String hasImage = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
+ if (!"yes".equals(hasImage)) {
+ throw new Exception("No images found in file " + filename);
+ }
+ assertEquals("Wrong image count", numImages,
+ Integer.parseInt(retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
+ assertEquals("Wrong width", width,
+ Integer.parseInt(retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)));
+ assertEquals("Wrong height", height,
+ Integer.parseInt(retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)));
+ assertEquals("Wrong rotation", rotation,
+ Integer.parseInt(retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)));
+ retriever.release();
+
+ if (useGrid) {
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(filename);
+ MediaFormat format = extractor.getTrackFormat(0);
+ int tileWidth = format.getInteger(MediaFormat.KEY_TILE_WIDTH);
+ int tileHeight = format.getInteger(MediaFormat.KEY_TILE_HEIGHT);
+ int gridRows = format.getInteger(MediaFormat.KEY_GRID_ROWS);
+ int gridCols = format.getInteger(MediaFormat.KEY_GRID_COLUMNS);
+ assertTrue("Wrong tile width or grid cols",
+ ((width + tileWidth - 1) / tileWidth) == gridCols);
+ assertTrue("Wrong tile height or grid rows",
+ ((height + tileHeight - 1) / tileHeight) == gridRows);
+ extractor.release();
+ }
+ }
+}
diff --git a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
index 1e6f05e..31d6848 100644
--- a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
+++ b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
@@ -39,6 +39,13 @@
namespace android {
namespace {
+// The 'stride' field is ignored by AHardwareBuffer_allocate, so we can use it
+// to pass these flags.
+enum TestFlags {
+ kGlFormat = 0x1, // The 'format' field specifies a GL format.
+ kUseSrgb = 0x2, // Whether to use the sRGB transfer function.
+};
+
#define FORMAT_CASE(x) case x: return #x; break
const char* AHBFormatAsString(int32_t format) {
switch (format) {
@@ -131,6 +138,7 @@
// Uploads opaque red to the currently bound texture.
void UploadRedPixels(const AHardwareBuffer_Desc& desc) {
+ const bool use_srgb = desc.stride & kUseSrgb;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
switch (desc.format) {
case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
@@ -141,7 +149,7 @@
const int size = desc.width * desc.height * 3;
std::unique_ptr<uint8_t[]> pixels(new uint8_t[size]);
for (int i = 0; i < size; i += 3) {
- pixels[i] = 255;
+ pixels[i] = use_srgb ? 188 : 255;
pixels[i + 1] = 0;
pixels[i + 2] = 0;
}
@@ -154,10 +162,10 @@
const int size = desc.width * desc.height * 4;
std::unique_ptr<uint8_t[]> pixels(new uint8_t[size]);
for (int i = 0; i < size; i += 4) {
- pixels[i] = 255;
+ pixels[i] = use_srgb ? 188 : 255;
pixels[i + 1] = 0;
pixels[i + 2] = 0;
- pixels[i + 3] = 255;
+ pixels[i + 3] = use_srgb ? 128 : 255;
}
UploadData(desc, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get());
break;
@@ -193,28 +201,35 @@
// Draws the following checkerboard pattern using glScissor and glClear.
// The number after the color is the stencil value and the floating point number is the depth value.
+// The pattern is asymmetric to detect coordinate system mixups.
// +-----+-----+ (W, H)
-// | OR1 | Ob2 |
+// | OR1 | OB2 |
// | 0.5 | 0.0 |
-// +-----+-----+ TB = transparent black
-// | TB0 | OR1 | OR = opaque red
-// | 1.0 | 0.5 | Ob = opaque blue
-// (0, 0) +-----+-----+
+// +-----+-----+ Tb = transparent black
+// | Tb0 | OG3 | OR = opaque red
+// | 1.0 | 1.0 | OG = opaque green
+// (0, 0) +-----+-----+ OB = opaque blue
//
void DrawCheckerboard(int width, int height) {
glEnable(GL_SCISSOR_TEST);
const GLbitfield all_bits = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
- glClearColor(1.f, 0.f, 0.f, 1.f);
- glClearDepthf(0.5f);
- glClearStencil(1);
- glScissor(0, 0, width, height);
- glClear(all_bits);
-
glClearColor(0.f, 0.f, 0.f, 0.f);
glClearDepthf(1.0f);
glClearStencil(0);
- glScissor(0, 0, width / 2, height / 2);
+ glScissor(0, 0, width, height);
+ glClear(all_bits);
+
+ glClearColor(1.f, 0.f, 0.f, 1.f);
+ glClearDepthf(0.5f);
+ glClearStencil(1);
+ glScissor(0, height / 2, width / 2, height / 2);
+ glClear(all_bits);
+
+ glClearColor(0.f, 1.f, 0.f, 1.f);
+ glClearDepthf(1.0f);
+ glClearStencil(3);
+ glScissor(width / 2, 0, width / 2, height / 2);
glClear(all_bits);
glClearColor(0.f, 0.f, 1.f, 1.f);
@@ -231,9 +246,11 @@
kZero, // all zero, i.e., transparent black
kBlack, // opaque black
kRed, // opaque red
+ kGreen, // opaque green
kBlue, // opaque blue
kRed50, // 50% premultiplied red, i.e., (0.5, 0, 0, 0.5)
kRed50Srgb, // 50% premultiplied red under sRGB transfer function
+ kRed50Alpha100, // Opaque 50% red
};
struct GoldenPixel {
@@ -274,6 +291,7 @@
switch (golden.color) {
case kRed: golden_pixel[0] = 255; break;
case kRed50:
+ case kRed50Alpha100:
use_range = true;
golden_pixel[0] = 127;
golden_max[0] = 128;
@@ -283,6 +301,7 @@
golden_pixel[0] = 187;
golden_max[0] = 188;
break;
+ case kGreen: golden_pixel[1] = 255; break;
case kBlue: golden_pixel[2] = 255; break;
case kZero: if (FormatHasAlpha(format)) golden_pixel[3] = 0; break;
case kBlack: break;
@@ -311,6 +330,7 @@
switch (golden.color) {
case kRed: golden_pixel[0] = 1.f; break;
case kRed50: golden_pixel[0] = 0.5f; golden_pixel[3] = 0.5f; break;
+ case kGreen: golden_pixel[1] = 1.f; break;
case kBlue: golden_pixel[2] = 1.f; break;
case kZero: golden_pixel[3] = 0.f; break;
case kBlack: break;
@@ -528,7 +548,7 @@
void main() {
uvec4 stencil = texture(uTexture, vTexCoords);
color.r = stencil.x == 1u ? 1.0 : 0.0;
- color.g = 0.0;
+ color.g = stencil.x == 3u ? 1.0 : 0.0;
color.b = stencil.x == 2u ? 1.0 : 0.0;
color.a = stencil.x == 0u ? 0.0 : 1.0;
}
@@ -543,7 +563,7 @@
void main() {
uvec4 stencil = texture(uTexture, vec3(vTexCoords, uLayer));
color.r = stencil.x == 1u ? 1.0 : 0.0;
- color.g = 0.0;
+ color.g = stencil.x == 3u ? 1.0 : 0.0;
color.b = stencil.x == 2u ? 1.0 : 0.0;
color.a = stencil.x == 0u ? 0.0 : 1.0;
}
@@ -586,7 +606,7 @@
};
void SetUp() override;
- virtual bool SetUpBuffer(const AHardwareBuffer_Desc& desc, bool use_srgb = false);
+ virtual bool SetUpBuffer(const AHardwareBuffer_Desc& desc);
void SetUpProgram(const std::string& vertex_source, const std::string& fragment_source,
const float* mesh, float scale, int texture_unit = 0);
void SetUpTexture(const AHardwareBuffer_Desc& desc, int unit);
@@ -597,7 +617,7 @@
void TearDown() override;
void MakeCurrent(int which) {
- if (GetParam().stride != 0) return;
+ if (GetParam().stride & kGlFormat) return;
mWhich = which;
eglMakeCurrent(mDisplay, mSurface, mSurface, mContext[mWhich]);
}
@@ -607,6 +627,17 @@
bool HasGLExtension(const std::string& s) {
return mGLExtensions.find(s) != mGLExtensions.end();
}
+ bool IsFormatColorRenderable(uint32_t format, bool use_srgb) {
+ if (use_srgb) {
+ // According to the spec, GL_SRGB8 is not color-renderable.
+ return format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM || format == GL_SRGB8_ALPHA8;
+ } else {
+ if (format == GL_RGBA16F || format == AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT) {
+ return mGLVersion >= 32 || HasGLExtension("GL_EXT_color_buffer_float");
+ }
+ return true;
+ }
+ }
protected:
std::set<std::string> mEGLExtensions;
@@ -622,6 +653,7 @@
EGLImageKHR mEGLImage = EGL_NO_IMAGE_KHR;
GLenum mTexTarget = GL_NONE;
GLuint mProgram = 0;
+ GLint mColorLocation = -1;
GLint mFaceVectorLocation = -1;
GLuint mTextures[2] = { 0, 0 };
GLuint mBufferObjects[2] = { 0, 0 };
@@ -694,7 +726,8 @@
ASSERT_GE(mGLVersion, 20);
}
-bool AHardwareBufferGLTest::SetUpBuffer(const AHardwareBuffer_Desc& desc, bool use_srgb) {
+bool AHardwareBufferGLTest::SetUpBuffer(const AHardwareBuffer_Desc& desc) {
+ const bool use_srgb = desc.stride & kUseSrgb;
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_CUBE_MAP) {
if (desc.layers > 6) {
if (mGLVersion < 32) {
@@ -728,16 +761,16 @@
mGLVersion / 10, mGLVersion % 10);
return false;
}
- if (desc.format == GL_RGBA16F &&
- (!HasGLExtension("GL_EXT_color_buffer_float") && mGLVersion < 32)) {
- ALOGI("Test skipped: GL_RGBA16F requires GL_EXT_color_buffer_float or GL ES 3.2");
+ if (desc.format == GL_RGBA16F && mGLVersion < 30) {
+ ALOGI("Test skipped: GL_RGBA16F requires GL ES 3.0, found %d.%d",
+ mGLVersion / 10, mGLVersion % 10);
return false;
}
- // Nonzero stride indicates that desc.format should be interpreted as a GL format
- // and the test should be run in a single context, without using AHardwareBuffer.
- // This simplifies verifying that the test behaves as expected even if the
- // AHardwareBuffer format under test is not supported.
- if (desc.stride != 0) {
+ // For control cases using GL formats, the test should be run in a single
+ // context, without using AHardwareBuffer. This simplifies verifying that
+ // the test behaves as expected even if the AHardwareBuffer format under
+ // test is not supported.
+ if (desc.stride & kGlFormat) {
mContextCount = 1;
return true;
}
@@ -821,9 +854,9 @@
FAIL() << "Unknown mesh";
}
glUniform1f(glGetUniformLocation(mProgram, "uScale"), scale);
- GLint u_color_location = glGetUniformLocation(mProgram, "uColor");
- if (u_color_location >= 0) {
- glUniform4f(u_color_location, 1.f, 0.f, 0.f, 1.f);
+ mColorLocation = glGetUniformLocation(mProgram, "uColor");
+ if (mColorLocation >= 0) {
+ glUniform4f(mColorLocation, 1.f, 0.f, 0.f, 1.f);
}
GLint u_texture_location = glGetUniformLocation(mProgram, "uTexture");
if (u_texture_location >= 0) {
@@ -841,14 +874,7 @@
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(mTexTarget, texture);
- if (desc.stride == 0) {
- if (HasGLExtension("GL_EXT_EGL_image_storage")) {
- glEGLImageTargetTexStorageEXT(mTexTarget, static_cast<GLeglImageOES>(mEGLImage),
- nullptr);
- } else {
- glEGLImageTargetTexture2DOES(mTexTarget, static_cast<GLeglImageOES>(mEGLImage));
- }
- } else {
+ if (desc.stride & kGlFormat) {
int levels = 1;
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE) {
levels = MipLevelCount(desc.width, desc.height);
@@ -894,6 +920,13 @@
}
}
}
+ } else {
+ if (HasGLExtension("GL_EXT_EGL_image_storage")) {
+ glEGLImageTargetTexStorageEXT(mTexTarget, static_cast<GLeglImageOES>(mEGLImage),
+ nullptr);
+ } else {
+ glEGLImageTargetTexture2DOES(mTexTarget, static_cast<GLeglImageOES>(mEGLImage));
+ }
}
ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
}
@@ -946,11 +979,11 @@
GLuint renderbuffer = 0;
glGenRenderbuffers(1, &renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
- if (GetParam().stride == 0) {
+ if (GetParam().stride & kGlFormat) {
+ glRenderbufferStorage(GL_RENDERBUFFER, GetParam().format, width, height);
+ } else {
glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER,
static_cast<GLeglImageOES>(mEGLImage));
- } else {
- glRenderbufferStorage(GL_RENDERBUFFER, GetParam().format, width, height);
}
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment_points[i],
GL_RENDERBUFFER, renderbuffer);
@@ -994,9 +1027,9 @@
class AHardwareBufferBlobFormatTest : public AHardwareBufferGLTest {
public:
- bool SetUpBuffer(const AHardwareBuffer_Desc& desc, bool use_srgb = false) override {
+ bool SetUpBuffer(const AHardwareBuffer_Desc& desc) override {
if (!HasGLExtension("GL_EXT_external_buffer")) return false;
- return AHardwareBufferGLTest::SetUpBuffer(desc, use_srgb);
+ return AHardwareBufferGLTest::SetUpBuffer(desc);
}
};
@@ -1135,7 +1168,17 @@
AHardwareBuffer_Desc{1, 1, 1, AHARDWAREBUFFER_FORMAT_BLOB, 0, 0, 0, 0}));
-class AHardwareBufferColorFormatTest : public AHardwareBufferGLTest {};
+class AHardwareBufferColorFormatTest : public AHardwareBufferGLTest {
+public:
+ bool SetUpBuffer(const AHardwareBuffer_Desc& desc) override {
+ if ((desc.usage & AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT) &&
+ !IsFormatColorRenderable(desc.format, desc.stride & kUseSrgb)) {
+ ALOGI("Test skipped: requires GPU_COLOR_OUTPUT, but format is not color-renderable");
+ return false;
+ }
+ return AHardwareBufferGLTest::SetUpBuffer(desc);
+ }
+};
// Verify that when allocating an AHardwareBuffer succeeds with GPU_COLOR_OUTPUT,
// it can be bound as a framebuffer attachment, glClear'ed and then read from
@@ -1161,10 +1204,10 @@
MakeCurrent(0);
std::vector<GoldenPixel> goldens{
- {10, 90, kRed}, {40, 90, kRed}, {60, 90, kBlue}, {90, 90, kBlue},
- {10, 60, kRed}, {40, 60, kRed}, {60, 60, kBlue}, {90, 60, kBlue},
- {10, 40, kZero}, {40, 40, kZero}, {60, 40, kRed}, {90, 40, kRed},
- {10, 10, kZero}, {40, 10, kZero}, {60, 10, kRed}, {90, 10, kRed},
+ {10, 90, kRed}, {40, 90, kRed}, {60, 90, kBlue}, {90, 90, kBlue},
+ {10, 60, kRed}, {40, 60, kRed}, {60, 60, kBlue}, {90, 60, kBlue},
+ {10, 40, kZero}, {40, 40, kZero}, {60, 40, kGreen}, {90, 40, kGreen},
+ {10, 10, kZero}, {40, 10, kZero}, {60, 10, kGreen}, {90, 10, kGreen},
};
CheckGoldenPixels(goldens, desc.format);
}
@@ -1176,7 +1219,7 @@
desc.height = 10;
desc.usage = AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT | AHARDWAREBUFFER_USAGE_CPU_READ_RARELY;
// This test does not make sense for GL formats. Layered buffers do not support CPU access.
- if (desc.stride != 0 || desc.layers > 1) return;
+ if ((desc.stride & kGlFormat) || desc.layers > 1) return;
if (!SetUpBuffer(desc)) return;
MakeCurrent(1);
@@ -1195,10 +1238,10 @@
ASSERT_EQ(NO_ERROR, result);
std::vector<GoldenPixel> goldens{
- {0, 9, kRed}, {4, 9, kRed}, {5, 9, kBlue}, {9, 9, kBlue},
- {0, 5, kRed}, {4, 5, kRed}, {5, 5, kBlue}, {9, 5, kBlue},
- {0, 4, kZero}, {4, 4, kZero}, {5, 4, kRed}, {9, 4, kRed},
- {0, 0, kZero}, {4, 0, kZero}, {5, 0, kRed}, {9, 0, kRed},
+ {0, 9, kRed}, {4, 9, kRed}, {5, 9, kBlue}, {9, 9, kBlue},
+ {0, 5, kRed}, {4, 5, kRed}, {5, 5, kBlue}, {9, 5, kBlue},
+ {0, 4, kZero}, {4, 4, kZero}, {5, 4, kGreen}, {9, 4, kGreen},
+ {0, 0, kZero}, {4, 0, kZero}, {5, 0, kGreen}, {9, 0, kGreen},
};
for (const GoldenPixel& golden : goldens) {
ptrdiff_t row_offset = golden.y * desc.stride;
@@ -1226,9 +1269,9 @@
uint16_t* pixel = reinterpret_cast<uint16_t*>(
reinterpret_cast<uint8_t*>(data) + (row_offset + golden.x) * 2);
std::array<uint8_t, 4> pixel_to_check = {
- static_cast<uint8_t>(((*pixel & 0xF800) >> 11) * (255.f/31.f)),
- static_cast<uint8_t>(((*pixel & 0x07E0) >> 5) * (255.f/63.f)),
- static_cast<uint8_t>((*pixel & 0x001F) * (255.f/31.f)),
+ static_cast<uint8_t>(((*pixel & 0xF800) >> 11) * (255./31.)),
+ static_cast<uint8_t>(((*pixel & 0x07E0) >> 5) * (255./63.)),
+ static_cast<uint8_t>((*pixel & 0x001F) * (255./31.)),
255,
};
CheckGoldenPixel(golden, pixel_to_check, desc.format);
@@ -1248,10 +1291,10 @@
uint32_t* pixel = reinterpret_cast<uint32_t*>(
reinterpret_cast<uint8_t*>(data) + (row_offset + golden.x) * 4);
std::array<uint8_t, 4> pixel_to_check = {
- static_cast<uint8_t>((*pixel & 0x000003FF) * (255.f/1023.f)),
- static_cast<uint8_t>(((*pixel & 0x000FFC00) >> 10) * (255.f/1023.f)),
- static_cast<uint8_t>(((*pixel & 0x3FF00000) >> 20) * (255.f/1023.f)),
- static_cast<uint8_t>(((*pixel & 0xC0000000) >> 30) * (255.f/3.f)),
+ static_cast<uint8_t>((*pixel & 0x000003FF) * (255./1023.)),
+ static_cast<uint8_t>(((*pixel & 0x000FFC00) >> 10) * (255./1023.)),
+ static_cast<uint8_t>(((*pixel & 0x3FF00000) >> 20) * (255./1023.)),
+ static_cast<uint8_t>(((*pixel & 0xC0000000) >> 30) * (255./3.)),
};
CheckGoldenPixel(golden, pixel_to_check, desc.format);
break;
@@ -1296,10 +1339,14 @@
EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
// Check the rendered pixels. There should be a red square in the middle.
+ GoldenColor color = kRed;
+ if (desc.stride & kUseSrgb) {
+ color = FormatHasAlpha(desc.format) ? kRed50 : kRed50Alpha100;
+ }
std::vector<GoldenPixel> goldens{
{5, 35, kZero}, {15, 35, kZero}, {25, 35, kZero}, {35, 35, kZero},
- {5, 25, kZero}, {15, 25, kRed}, {25, 25, kRed}, {35, 25, kZero},
- {5, 15, kZero}, {15, 15, kRed}, {25, 15, kRed}, {35, 15, kZero},
+ {5, 25, kZero}, {15, 25, color}, {25, 25, color}, {35, 25, kZero},
+ {5, 15, kZero}, {15, 15, color}, {25, 15, color}, {35, 15, kZero},
{5, 5, kZero}, {15, 5, kZero}, {25, 5, kZero}, {35, 5, kZero},
};
CheckGoldenPixels(goldens, GL_RGBA8);
@@ -1344,10 +1391,10 @@
// format has an alpha channel.
const GoldenColor kCBBlack = FormatHasAlpha(desc.format) ? kZero : kBlack;
std::vector<GoldenPixel> goldens{
- {5, 35, kZero}, {15, 35, kZero}, {25, 35, kZero}, {35, 35, kZero},
- {5, 25, kZero}, {15, 25, kRed}, {25, 25, kBlue}, {35, 25, kZero},
- {5, 15, kZero}, {15, 15, kCBBlack}, {25, 15, kRed}, {35, 15, kZero},
- {5, 5, kZero}, {15, 5, kZero}, {25, 5, kZero}, {35, 5, kZero},
+ {5, 35, kZero}, {15, 35, kZero}, {25, 35, kZero}, {35, 35, kZero},
+ {5, 25, kZero}, {15, 25, kRed}, {25, 25, kBlue}, {35, 25, kZero},
+ {5, 15, kZero}, {15, 15, kCBBlack}, {25, 15, kGreen}, {35, 15, kZero},
+ {5, 5, kZero}, {15, 5, kZero}, {25, 5, kZero}, {35, 5, kZero},
};
CheckGoldenPixels(goldens, GL_RGBA8);
}
@@ -1363,10 +1410,7 @@
AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT |
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE;
- const bool kUseSrgb = desc.format == GL_SRGB8_ALPHA8 ||
- desc.format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM ||
- desc.format == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
- if (!SetUpBuffer(desc, kUseSrgb)) return;
+ if (!SetUpBuffer(desc)) return;
const int kTextureUnit = 7;
for (int i = 0; i < mContextCount; ++i) {
@@ -1395,7 +1439,7 @@
MakeCurrent(0);
SetUpFramebuffer(1, 1, desc.layers - 1, kBufferAsTexture, kNone, kNone, kNone,
MipLevelCount(desc.width, desc.height) - 1);
- std::vector<GoldenPixel> goldens{{0, 0, kUseSrgb ? kRed50Srgb : kRed50}};
+ std::vector<GoldenPixel> goldens{{0, 0, (desc.stride & kUseSrgb) ? kRed50Srgb : kRed50}};
CheckGoldenPixels(goldens, desc.format);
}
@@ -1439,10 +1483,10 @@
const GoldenColor kCBBlack = FormatHasAlpha(desc.format) ? kZero : kBlack;
std::vector<GoldenPixel> goldens{
- {5, 35, kZero}, {15, 35, kZero}, {25, 35, kZero}, {35, 35, kZero},
- {5, 25, kZero}, {15, 25, kRed}, {25, 25, kBlue}, {35, 25, kZero},
- {5, 15, kZero}, {15, 15, kCBBlack}, {25, 15, kRed}, {35, 15, kZero},
- {5, 5, kZero}, {15, 5, kZero}, {25, 5, kZero}, {35, 5, kZero},
+ {5, 35, kZero}, {15, 35, kZero}, {25, 35, kZero}, {35, 35, kZero},
+ {5, 25, kZero}, {15, 25, kRed}, {25, 25, kBlue}, {35, 25, kZero},
+ {5, 15, kZero}, {15, 15, kCBBlack}, {25, 15, kGreen}, {35, 15, kZero},
+ {5, 5, kZero}, {15, 5, kZero}, {25, 5, kZero}, {35, 5, kZero},
};
CheckGoldenPixels(goldens, GL_RGBA8);
}
@@ -1461,10 +1505,7 @@
desc.width = (desc.width / kNumTiles) * kNumTiles;
desc.height = desc.width;
desc.layers *= 6;
- const bool kUseSrgb = desc.format == GL_SRGB8_ALPHA8 ||
- desc.format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM ||
- desc.format == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
- if (!SetUpBuffer(desc, kUseSrgb)) return;
+ if (!SetUpBuffer(desc)) return;
for (int i = 0; i < mContextCount; ++i) {
MakeCurrent(i);
@@ -1492,23 +1533,27 @@
for (int face = 0; face < 6; ++face) {
SetUpFramebuffer(1, 1, desc.layers - 6 + face, kBufferAsTexture, kNone, kNone, kNone,
MipLevelCount(desc.width, desc.height) - 1);
- std::vector<GoldenPixel> goldens{{0, 0, kUseSrgb ? kRed50Srgb : kRed50}};
+ std::vector<GoldenPixel> goldens{{0, 0, (desc.stride & kUseSrgb) ? kRed50Srgb : kRed50}};
CheckGoldenPixels(goldens, desc.format);
}
}
+// The 'stride' field is used to pass a combination of TestFlags.
INSTANTIATE_TEST_CASE_P(
SingleLayer,
AHardwareBufferColorFormatTest,
::testing::Values(
- AHardwareBuffer_Desc{75, 33, 1, GL_RGB8, 0, 1, 0, 0},
- AHardwareBuffer_Desc{64, 80, 1, GL_RGBA8, 0, 1, 0, 0},
- AHardwareBuffer_Desc{49, 23, 1, GL_SRGB8_ALPHA8, 0, 1, 0, 0},
- AHardwareBuffer_Desc{42, 41, 1, GL_RGBA16F, 0, 1, 0, 0},
- AHardwareBuffer_Desc{37, 63, 1, GL_RGB10_A2, 0, 1, 0, 0},
+ AHardwareBuffer_Desc{75, 33, 1, GL_RGB8, 0, kGlFormat, 0, 0},
+ AHardwareBuffer_Desc{64, 80, 1, GL_RGBA8, 0, kGlFormat, 0, 0},
+ AHardwareBuffer_Desc{49, 23, 1, GL_SRGB8_ALPHA8, 0, kGlFormat | kUseSrgb, 0, 0},
+ AHardwareBuffer_Desc{42, 41, 1, GL_RGBA16F, 0, kGlFormat, 0, 0},
+ AHardwareBuffer_Desc{37, 63, 1, GL_RGB10_A2, 0, kGlFormat, 0, 0},
AHardwareBuffer_Desc{33, 20, 1, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, 0, 0, 0},
+ AHardwareBuffer_Desc{33, 20, 1, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, kUseSrgb, 0, 0},
AHardwareBuffer_Desc{20, 10, 1, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM, 0, 0, 0, 0},
+ AHardwareBuffer_Desc{20, 10, 1, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM, 0, kUseSrgb, 0, 0},
AHardwareBuffer_Desc{16, 20, 1, AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM, 0, 0, 0, 0},
+ AHardwareBuffer_Desc{16, 20, 1, AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM, 0, kUseSrgb, 0, 0},
AHardwareBuffer_Desc{10, 20, 1, AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM, 0, 0, 0, 0},
AHardwareBuffer_Desc{10, 20, 1, AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT, 0, 0, 0, 0},
AHardwareBuffer_Desc{10, 20, 1, AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM, 0, 0, 0, 0}));
@@ -1517,15 +1562,17 @@
MultipleLayers,
AHardwareBufferColorFormatTest,
::testing::Values(
- AHardwareBuffer_Desc{75, 33, 5, GL_RGB8, 0, 1, 0, 0},
- AHardwareBuffer_Desc{64, 80, 6, GL_RGBA8, 0, 1, 0, 0},
- AHardwareBuffer_Desc{33, 28, 4, GL_SRGB8_ALPHA8, 0, 1, 0, 0},
- AHardwareBuffer_Desc{42, 41, 3, GL_RGBA16F, 0, 1, 0, 0},
- AHardwareBuffer_Desc{37, 63, 4, GL_RGB10_A2, 0, 1, 0, 0},
+ AHardwareBuffer_Desc{75, 33, 5, GL_RGB8, 0, kGlFormat, 0, 0},
+ AHardwareBuffer_Desc{64, 80, 6, GL_RGBA8, 0, kGlFormat, 0, 0},
+ AHardwareBuffer_Desc{33, 28, 4, GL_SRGB8_ALPHA8, 0, kGlFormat | kUseSrgb, 0, 0},
+ AHardwareBuffer_Desc{42, 41, 3, GL_RGBA16F, 0, kGlFormat, 0, 0},
+ AHardwareBuffer_Desc{37, 63, 4, GL_RGB10_A2, 0, kGlFormat, 0, 0},
AHardwareBuffer_Desc{25, 77, 7, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, 0, 0, 0},
- AHardwareBuffer_Desc{64, 64, 4, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, 0, 0, 0},
+ AHardwareBuffer_Desc{25, 77, 7, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, kUseSrgb, 0, 0},
AHardwareBuffer_Desc{30, 30, 3, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM, 0, 0, 0, 0},
+ AHardwareBuffer_Desc{30, 30, 3, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM, 0, kUseSrgb, 0, 0},
AHardwareBuffer_Desc{50, 50, 4, AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM, 0, 0, 0, 0},
+ AHardwareBuffer_Desc{50, 50, 4, AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM, 0, kUseSrgb, 0, 0},
AHardwareBuffer_Desc{20, 10, 2, AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM, 0, 0, 0, 0},
AHardwareBuffer_Desc{20, 20, 4, AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT, 0, 0, 0, 0},
AHardwareBuffer_Desc{30, 20, 16, AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM, 0, 0, 0, 0}));
@@ -1569,7 +1616,7 @@
std::vector<GoldenPixel> goldens{
{5, 35, kRed}, {15, 35, kRed}, {25, 35, kZero}, {35, 35, kZero},
{5, 25, kRed}, {15, 25, kZero}, {25, 25, kZero}, {35, 25, kZero},
- {5, 15, kRed}, {15, 15, kRed}, {25, 15, kZero}, {35, 15, kRed},
+ {5, 15, kRed}, {15, 15, kRed}, {25, 15, kRed}, {35, 15, kRed},
{5, 5, kRed}, {15, 5, kRed}, {25, 5, kRed}, {35, 5, kRed},
};
CheckGoldenPixels(goldens, GL_RGBA8);
@@ -1684,12 +1731,12 @@
}
}
-// See comment in SetUpBuffer for explanation of nonzero stride and GL format.
+// The 'stride' field is used to pass a combination of TestFlags.
INSTANTIATE_TEST_CASE_P(
SingleLayer,
AHardwareBufferDepthFormatTest,
::testing::Values(
- AHardwareBuffer_Desc{16, 24, 1, GL_DEPTH_COMPONENT16, 0, 1, 0, 0},
+ AHardwareBuffer_Desc{16, 24, 1, GL_DEPTH_COMPONENT16, 0, kGlFormat, 0, 0},
AHardwareBuffer_Desc{16, 24, 1, AHARDWAREBUFFER_FORMAT_D16_UNORM, 0, 0, 0, 0},
AHardwareBuffer_Desc{44, 21, 1, AHARDWAREBUFFER_FORMAT_D24_UNORM, 0, 0, 0, 0},
AHardwareBuffer_Desc{57, 33, 1, AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT, 0, 0, 0, 0},
@@ -1701,7 +1748,7 @@
MultipleLayers,
AHardwareBufferDepthFormatTest,
::testing::Values(
- AHardwareBuffer_Desc{16, 24, 6, GL_DEPTH_COMPONENT16, 0, 1, 0, 0},
+ AHardwareBuffer_Desc{16, 24, 6, GL_DEPTH_COMPONENT16, 0, kGlFormat, 0, 0},
AHardwareBuffer_Desc{16, 24, 6, AHARDWAREBUFFER_FORMAT_D16_UNORM, 0, 0, 0, 0},
AHardwareBuffer_Desc{44, 21, 4, AHARDWAREBUFFER_FORMAT_D24_UNORM, 0, 0, 0, 0},
AHardwareBuffer_Desc{57, 33, 7, AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT, 0, 0, 0, 0},
@@ -1746,14 +1793,18 @@
glStencilFunc(GL_EQUAL, 2, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
+ glUniform4f(mColorLocation, 0.f, 1.f, 0.f, 1.f);
+ glStencilFunc(GL_EQUAL, 4, 0xFF);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
// Check golden pixels.
std::vector<GoldenPixel> goldens{
- {5, 35, kRed}, {15, 35, kRed}, {25, 35, kZero}, {35, 35, kZero},
- {5, 25, kRed}, {15, 25, kRed}, {25, 25, kZero}, {35, 25, kZero},
- {5, 15, kZero}, {15, 15, kZero}, {25, 15, kRed}, {35, 15, kRed},
- {5, 5, kZero}, {15, 5, kZero}, {25, 5, kRed}, {35, 5, kRed},
+ {5, 35, kRed}, {15, 35, kRed}, {25, 35, kZero}, {35, 35, kZero},
+ {5, 25, kRed}, {15, 25, kRed}, {25, 25, kZero}, {35, 25, kZero},
+ {5, 15, kZero}, {15, 15, kZero}, {25, 15, kGreen}, {35, 15, kGreen},
+ {5, 5, kZero}, {15, 5, kZero}, {25, 5, kGreen}, {35, 5, kGreen},
};
CheckGoldenPixels(goldens, GL_RGBA8);
}
@@ -1801,21 +1852,21 @@
// Check golden pixels.
std::vector<GoldenPixel> goldens{
- {5, 35, kRed}, {15, 35, kRed}, {25, 35, kBlue}, {35, 35, kBlue},
- {5, 25, kRed}, {15, 25, kRed}, {25, 25, kBlue}, {35, 25, kBlue},
- {5, 15, kZero}, {15, 15, kZero}, {25, 15, kRed}, {35, 15, kRed},
- {5, 5, kZero}, {15, 5, kZero}, {25, 5, kRed}, {35, 5, kRed},
+ {5, 35, kRed}, {15, 35, kRed}, {25, 35, kBlue}, {35, 35, kBlue},
+ {5, 25, kRed}, {15, 25, kRed}, {25, 25, kBlue}, {35, 25, kBlue},
+ {5, 15, kZero}, {15, 15, kZero}, {25, 15, kGreen}, {35, 15, kGreen},
+ {5, 5, kZero}, {15, 5, kZero}, {25, 5, kGreen}, {35, 5, kGreen},
};
CheckGoldenPixels(goldens, GL_RGBA8);
}
-// See comment in SetUpBuffer for explanation of nonzero stride and GL format.
+// The 'stride' field is used to pass a combination of TestFlags.
INSTANTIATE_TEST_CASE_P(
SingleLayer,
AHardwareBufferStencilFormatTest,
::testing::Values(
- AHardwareBuffer_Desc{49, 57, 1, GL_STENCIL_INDEX8, 0, 1, 0, 0},
- AHardwareBuffer_Desc{36, 50, 1, GL_DEPTH24_STENCIL8, 0, 1, 0, 0},
+ AHardwareBuffer_Desc{49, 57, 1, GL_STENCIL_INDEX8, 0, kGlFormat, 0, 0},
+ AHardwareBuffer_Desc{36, 50, 1, GL_DEPTH24_STENCIL8, 0, kGlFormat, 0, 0},
AHardwareBuffer_Desc{26, 29, 1, AHARDWAREBUFFER_FORMAT_S8_UINT, 0, 0, 0, 0},
AHardwareBuffer_Desc{57, 33, 1, AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT, 0, 0, 0, 0},
AHardwareBuffer_Desc{17, 23, 1, AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT, 0, 0, 0, 0}));
@@ -1824,8 +1875,8 @@
MultipleLayers,
AHardwareBufferStencilFormatTest,
::testing::Values(
- AHardwareBuffer_Desc{49, 57, 3, GL_STENCIL_INDEX8, 0, 1, 0, 0},
- AHardwareBuffer_Desc{36, 50, 6, GL_DEPTH24_STENCIL8, 0, 1, 0, 0},
+ AHardwareBuffer_Desc{49, 57, 3, GL_STENCIL_INDEX8, 0, kGlFormat, 0, 0},
+ AHardwareBuffer_Desc{36, 50, 6, GL_DEPTH24_STENCIL8, 0, kGlFormat, 0, 0},
AHardwareBuffer_Desc{26, 29, 5, AHARDWAREBUFFER_FORMAT_S8_UINT, 0, 0, 0, 0},
AHardwareBuffer_Desc{57, 33, 4, AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT, 0, 0, 0, 0},
AHardwareBuffer_Desc{17, 23, 7, AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT, 0, 0, 0, 0}));
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
index c435a8a..1a08d3d 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -70,7 +70,7 @@
private static final int STATE_WIFI_ENABLED = 2;
private static final int STATE_WIFI_DISABLED = 3;
private static final int STATE_SCANNING = 4;
- private static final int STATE_SCAN_RESULTS_AVAILABLE = 5;
+ private static final int STATE_SCAN_DONE = 5;
private static final String TAG = "WifiManagerTest";
private static final String SSID1 = "\"WifiManagerTest\"";
@@ -96,13 +96,15 @@
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+
synchronized (mMySync) {
- if (mWifiManager.getScanResults() != null) {
+ if (intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)) {
mScanResults = mWifiManager.getScanResults();
- mMySync.expectedState = STATE_SCAN_RESULTS_AVAILABLE;
- mScanResults = mWifiManager.getScanResults();
- mMySync.notifyAll();
+ } else {
+ mScanResults = null;
}
+ mMySync.expectedState = STATE_SCAN_DONE;
+ mMySync.notifyAll();
}
} else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
int newState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
diff --git a/tests/tests/os/AndroidTest.xml b/tests/tests/os/AndroidTest.xml
index 680e88b..8f8d997 100644
--- a/tests/tests/os/AndroidTest.xml
+++ b/tests/tests/os/AndroidTest.xml
@@ -23,6 +23,11 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.os.cts" />
<option name="runtime-hint" value="3m15s" />
+ <!-- Do not disable hidden-api-checks for this test. This test includes
+ StrictModeTest which relies on hidden API checks being enabled in
+ order to function properly. Any APIs used by this test should be
+ added to the light grey list, or made @TestApi instead.
<option name="hidden-api-checks" value="false" />
+ -->
</test>
</configuration>
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index cee7da6..39ca729 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -3941,6 +3941,11 @@
<permission android:name="android.permission.OPEN_APPLICATION_DETAILS_OPEN_BY_DEFAULT_PAGE"
android:protectionLevel="signature" />
+ <!-- Allows hidden API checks to be disabled when starting a process.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.DISABLE_HIDDEN_API_CHECKS"
+ android:protectionLevel="signature" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/tests/tests/security/res/raw/cve_2017_13309_client.bks b/tests/tests/security/res/raw/cve_2017_13309_client.bks
new file mode 100644
index 0000000..6f450d3
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2017_13309_client.bks
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2017_13309_server.bks b/tests/tests/security/res/raw/cve_2017_13309_server.bks
new file mode 100644
index 0000000..384c4be
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2017_13309_server.bks
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2017_13309_trustedcert.bks b/tests/tests/security/res/raw/cve_2017_13309_trustedcert.bks
new file mode 100644
index 0000000..539d36e
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2017_13309_trustedcert.bks
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java b/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java
new file mode 100644
index 0000000..8f6477e
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/SSLConscryptPlainTextExposureTest.java
@@ -0,0 +1,866 @@
+/*
+ * Copyright (C) 2018 The AndroCid Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.security.cts;
+
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import android.platform.test.annotations.SecurityTest;
+import android.test.InstrumentationTestCase;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.ByteArrayInputStream;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.spi.SelectorProvider;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Formatter;
+import java.util.Iterator;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManagerFactory;
+
+public class SSLConscryptPlainTextExposureTest extends InstrumentationTestCase {
+
+ private TestSSLServer mTestSSLServer;
+ private TestSSLConnection mTestSSLClient;
+ public static Context context;
+ public static String output = "";
+ private final String pattern = ".*PLAIN TEXT EXPOSED.*";
+
+ public void test_android_CVE_2017_13309() {
+
+ context = getInstrumentation().getContext();
+ mTestSSLServer = new TestSSLServer();
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ mTestSSLClient = new TestSSLConnection();
+ mTestSSLClient.StartConnect();
+ assertFalse("Pattern found",
+ Pattern.compile(pattern,
+ Pattern.DOTALL).matcher(output).matches());
+ }
+
+ public static InputStream getResource(final int resource){
+ return SSLConscryptPlainTextExposureTest.context.getResources().openRawResource(resource);
+ }
+
+ public static void setOutput(String data){
+ SSLConscryptPlainTextExposureTest.output = data;
+ }
+}
+
+
+class TestSSLConnection {
+
+ public SSLContext sslc;
+
+ public SocketChannel socketChannel;
+ public SSLEngine clientEngine;
+ public String remoteAddress = "127.0.0.1";
+ public int port = 9000;
+ public ByteBuffer[] dataOutAppBuffers = new ByteBuffer[3];
+ public ByteBuffer dataOutNetBuffer;
+ public ByteBuffer hsInAppBuffer, hsInNetBuffer, hsOutAppBuffer, hsOutNetBuffer;
+ public boolean isHandshaked = false;
+ public ExecutorService executor = Executors.newSingleThreadExecutor();
+ public InputStream clientKey = null;
+ public InputStream trustedCert = null;
+
+ public void StartConnect() {
+ KeyStore ks = null;
+
+ clientKey = SSLConscryptPlainTextExposureTest.getResource(R.raw.cve_2017_13309_client);
+ trustedCert = SSLConscryptPlainTextExposureTest.getResource(R.raw.cve_2017_13309_trustedcert);
+
+ try {
+ ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ }
+ KeyStore ts = null;
+ try {
+ ts = KeyStore.getInstance(KeyStore.getDefaultType());
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ ks.load(clientKey, "pocclient".toCharArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ try {
+ ts.load(trustedCert, "trusted".toCharArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ KeyManagerFactory kmf = null;
+ try {
+ kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ kmf.init(ks, "keypass".toCharArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ TrustManagerFactory tmf = null;
+ try {
+ tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ try {
+ tmf.init(ts);
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ }
+
+ SSLContext sslCtx = null;
+ try {
+ sslCtx = SSLContext.getInstance("TLSv1.2");
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ } catch (KeyManagementException e) {
+ e.printStackTrace();
+ }
+
+ sslc = sslCtx;
+
+ clientEngine = sslc.createSSLEngine(remoteAddress, port);
+ clientEngine.setUseClientMode(true);
+ SSLSession session = clientEngine.getSession();
+
+ hsOutAppBuffer = ByteBuffer.allocate(4096);
+ hsOutNetBuffer = ByteBuffer.allocate(session.getPacketBufferSize());
+ hsInAppBuffer = ByteBuffer.allocate(4096);
+ hsInNetBuffer = ByteBuffer.allocate(session.getPacketBufferSize());
+ dataOutNetBuffer = ByteBuffer.allocate(session.getPacketBufferSize());
+
+ try {
+ socketChannel = SocketChannel.open();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ socketChannel.configureBlocking(false);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ socketChannel.connect(new InetSocketAddress(remoteAddress, port));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ while(!socketChannel.finishConnect()) {
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ clientEngine.beginHandshake();
+ } catch (SSLException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ isHandshaked = doHandshake(socketChannel, clientEngine);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if(isHandshaked) {
+ dataOutAppBuffers[0] = ByteBuffer.wrap("PLAIN TEXT EXPOSED".getBytes());
+ dataOutAppBuffers[1] = ByteBuffer.wrap("PLAIN TEXT EXPOSED".getBytes());
+ dataOutAppBuffers[2] = ByteBuffer.wrap("PLAIN TEXT EXPOSED".getBytes());
+
+ while(dataOutAppBuffers[0].hasRemaining() || dataOutAppBuffers[1].hasRemaining() || dataOutAppBuffers[2].hasRemaining()) {
+ dataOutNetBuffer.clear();
+ SSLEngineResult result = null;
+ try {
+ result = clientEngine.wrap(dataOutAppBuffers, 0, 3, dataOutNetBuffer);
+ } catch (SSLException e) {
+ e.printStackTrace();
+ }
+ switch(result.getStatus()) {
+ case OK:
+ dataOutNetBuffer.flip();
+ String outbuff = new String("");
+ Formatter formatter = new Formatter();
+ for(int i = 0; i < dataOutNetBuffer.limit(); i++) {
+ outbuff += formatter.format("%02x ", dataOutNetBuffer.get(i)).toString();
+ }
+ String output = new String(dataOutNetBuffer.array());
+ SSLConscryptPlainTextExposureTest.setOutput(output);
+ break;
+ case BUFFER_OVERFLOW:
+ dataOutNetBuffer = enlargePacketBuffer(clientEngine, dataOutNetBuffer);
+ break;
+ case BUFFER_UNDERFLOW:
+ try {
+ throw new SSLException("Buffer underflow in sending data");
+ } catch (SSLException e) {
+ e.printStackTrace();
+ }
+ case CLOSED:
+ try {
+ closeConnection(socketChannel, clientEngine);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return;
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+ }
+ }
+ }
+
+ try {
+ clientKey.close();
+ } catch (IOException e){
+ e.printStackTrace();
+ }
+
+ try{
+ trustedCert.close();
+ } catch (IOException e){
+ e.printStackTrace();
+ }
+
+ try {
+ shutdown();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void shutdown() throws IOException {
+ closeConnection(socketChannel, clientEngine);
+ executor.shutdown();
+ }
+
+ public void closeConnection(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+ engine.closeOutbound();
+ doHandshake(socketChannel, engine);
+ socketChannel.close();
+ }
+
+ public boolean doHandshake(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+ SSLEngineResult result;
+ SSLEngineResult.HandshakeStatus handshakeStatus;
+ int appBufferSize = engine.getSession().getApplicationBufferSize();
+
+ ByteBuffer srcAppData = ByteBuffer.allocate(appBufferSize);
+ ByteBuffer dstAppData = ByteBuffer.allocate(appBufferSize);
+
+ srcAppData.clear();
+ dstAppData.clear();
+
+ handshakeStatus = engine.getHandshakeStatus();
+ while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED
+ && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+ switch(handshakeStatus) {
+ case NEED_UNWRAP:
+ if(socketChannel.read(hsInNetBuffer) < 0) {
+ if(engine.isInboundDone() && engine.isOutboundDone()) {
+ return false;
+ }
+ try {
+ engine.closeInbound();
+ } catch (SSLException e) {
+ Log.e("poc-test","Forced to close inbound. No proper SSL/TLS close notification message from peer");
+ }
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ hsInNetBuffer.flip();
+ try {
+ result = engine.unwrap(hsInNetBuffer, hsInAppBuffer);
+ hsInNetBuffer.compact();
+ handshakeStatus = result.getHandshakeStatus();
+ } catch (SSLException sslException) {
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ switch(result.getStatus()) {
+ case OK:
+ break;
+ case BUFFER_OVERFLOW:
+ hsInAppBuffer = enlargeApplicationBuffer(engine, hsInAppBuffer);
+ break;
+ case BUFFER_UNDERFLOW:
+ hsInNetBuffer = handleBufferUnderflow(engine, hsInNetBuffer);
+ break;
+ case CLOSED:
+ if (engine.isOutboundDone()) {
+ return false;
+ } else {
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+ }
+ case NEED_WRAP:
+ hsOutNetBuffer.clear();
+ try {
+ result = engine.wrap(hsOutAppBuffer, hsOutNetBuffer);
+ handshakeStatus = result.getHandshakeStatus();
+ } catch (SSLException sslException) {
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ switch(result.getStatus()) {
+ case OK:
+ hsOutNetBuffer.flip();
+ while(hsOutNetBuffer.hasRemaining()) {
+ socketChannel.write(hsOutNetBuffer);
+ }
+ break;
+ case BUFFER_OVERFLOW:
+ hsOutNetBuffer = enlargePacketBuffer(engine, hsOutNetBuffer);
+ break;
+ case BUFFER_UNDERFLOW:
+ throw new SSLException("Buffer underflow in handshake and wrap");
+ case CLOSED:
+ try {
+ hsOutNetBuffer.flip();
+ while(hsOutNetBuffer.hasRemaining()) {
+ socketChannel.write(hsOutNetBuffer);
+ }
+ hsInNetBuffer.clear();
+ } catch (Exception e) {
+ handshakeStatus = engine.getHandshakeStatus();
+ }
+ break;
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+ }
+ break;
+ case NEED_TASK:
+ Runnable task;
+ while((task = engine.getDelegatedTask()) != null) {
+ executor.execute(task);
+ }
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ case FINISHED:
+ break;
+ case NOT_HANDSHAKING:
+ break;
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + handshakeStatus);
+ }
+ }
+ return true;
+ }
+
+ public ByteBuffer enlargePacketBuffer(SSLEngine engine, ByteBuffer buffer) {
+ return enlargeBuffer(buffer, engine.getSession().getPacketBufferSize());
+ }
+
+ public ByteBuffer enlargeApplicationBuffer(SSLEngine engine, ByteBuffer buffer) {
+ return enlargeBuffer(buffer, engine.getSession().getApplicationBufferSize());
+ }
+
+ public ByteBuffer enlargeBuffer(ByteBuffer buffer, int bufferSize) {
+ if(bufferSize > buffer.capacity()) {
+ buffer = ByteBuffer.allocate(bufferSize);
+ }
+ else {
+ buffer = ByteBuffer.allocate(buffer.capacity() * 2);
+ }
+ return buffer;
+ }
+ public ByteBuffer handleBufferUnderflow(SSLEngine engine, ByteBuffer buffer) {
+ if(engine.getSession().getPacketBufferSize() < buffer.limit()) {
+ return buffer;
+ }
+ else {
+ ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer);
+ buffer.flip();
+ replaceBuffer.put(buffer);
+ return replaceBuffer;
+ }
+ }
+}
+
+class TestSSLServer {
+
+ public ServerRunnable serverRunnable;
+ Thread server;
+
+ public TestSSLServer() {
+ serverRunnable = new ServerRunnable();
+ server = new Thread(serverRunnable);
+ server.start();
+
+ try{
+ Thread.sleep(1000);
+ }catch(InterruptedException e){
+ e.printStackTrace();
+ }
+ }
+
+ protected void onCancelled(String result) {
+ serverRunnable.stop();
+ }
+}
+
+class ServerRunnable implements Runnable {
+
+ SSLServer server;
+ InputStream serverKey = null;
+ InputStream trustedCert = null;
+
+ public void run() {
+ try {
+ server = new SSLServer();
+ server.runServer();
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void stop() {
+ server.stopServer();
+ }
+}
+
+class SSLServer {
+ public SSLContext serverContext;
+
+ public ByteBuffer hsInAppBuffer, hsInNetBuffer, hsOutAppBuffer, hsOutNetBuffer;
+ public ByteBuffer dataInAppBuffer, dataInNetBuffer;
+
+ final String hostAddress = "127.0.0.1";
+ public int port = 9000;
+ public boolean bActive = false;
+
+ public Selector selector;
+ InputStream serverKey = null;
+ InputStream trustedCert = null;
+ public ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ public void stopServer() {
+ bActive = false;
+ executor.shutdown();
+ selector.wakeup();
+ }
+
+ public void runServer() {
+ KeyStore ks = null;
+
+ serverKey = SSLConscryptPlainTextExposureTest.getResource(R.raw.cve_2017_13309_server);
+ trustedCert = SSLConscryptPlainTextExposureTest.getResource(R.raw.cve_2017_13309_trustedcert);
+
+ try {
+ ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ }
+ KeyStore ts = null;
+ try {
+ ts = KeyStore.getInstance(KeyStore.getDefaultType());
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ ks.load(serverKey, "pocserver".toCharArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ try {
+ ts.load(trustedCert, "trusted".toCharArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ KeyManagerFactory kmf = null;
+ try {
+ kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ try {
+ kmf.init(ks, "keypass".toCharArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ TrustManagerFactory tmf = null;
+ try {
+ tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ try {
+ tmf.init(ts);
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ }
+
+ SSLContext sslCtx = null;
+ try {
+ sslCtx = SSLContext.getInstance("TLSv1.2");
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
+ } catch (KeyManagementException e) {
+ e.printStackTrace();
+ }
+
+ serverContext = sslCtx;
+
+ SSLSession dummySession = serverContext.createSSLEngine().getSession();
+
+ hsInAppBuffer = ByteBuffer.allocate(4096);
+ hsInNetBuffer = ByteBuffer.allocate(dummySession.getPacketBufferSize());
+ hsOutAppBuffer = ByteBuffer.allocate(4096);
+ hsOutNetBuffer = ByteBuffer.allocate(dummySession.getPacketBufferSize());
+ dataInAppBuffer = ByteBuffer.allocate(4096);
+ dataInNetBuffer = ByteBuffer.allocate(dummySession.getPacketBufferSize());
+ dummySession.invalidate();
+
+ try {
+ selector = SelectorProvider.provider().openSelector();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ ServerSocketChannel serverSocketChannel = null;
+ try {
+ serverSocketChannel = ServerSocketChannel.open();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ serverSocketChannel.configureBlocking(false);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ serverSocketChannel.socket().bind(new InetSocketAddress(hostAddress, port));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
+ } catch (ClosedChannelException e) {
+ e.printStackTrace();
+ }
+
+ bActive = true;
+
+ while(bActive) {
+ try {
+ selector.select();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
+ while (selectedKeys.hasNext()) {
+ SelectionKey key = selectedKeys.next();
+ selectedKeys.remove();
+ if (!key.isValid()) {
+ continue;
+ }
+ if (key.isAcceptable()) {
+ try {
+ accept(key);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else if (key.isReadable()) {
+ try {
+ read((SocketChannel) key.channel(), (SSLEngine) key.attachment());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ public void accept(SelectionKey key) throws Exception{
+ SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
+ socketChannel.configureBlocking(false);
+
+ SSLEngine engine = serverContext.createSSLEngine();
+ engine.setUseClientMode(false);
+ engine.beginHandshake();
+
+ socketChannel.register(selector, SelectionKey.OP_READ, engine);
+
+ if(doHandshake(socketChannel, engine)) {
+ socketChannel.register(selector, SelectionKey.OP_READ, engine);
+ }
+ else {
+ socketChannel.close();
+ }
+ }
+
+ public boolean doHandshake(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+ SSLEngineResult result = null;
+ SSLEngineResult.HandshakeStatus handshakeStatus;
+ int appBufferSize = engine.getSession().getApplicationBufferSize();
+
+ ByteBuffer srcAppData = ByteBuffer.allocate(appBufferSize);
+ ByteBuffer dstAppData = ByteBuffer.allocate(appBufferSize);
+
+ srcAppData.clear();
+ dstAppData.clear();
+
+ handshakeStatus = engine.getHandshakeStatus();
+ while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED
+ && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+
+ switch(handshakeStatus) {
+ case NEED_UNWRAP:
+ if(socketChannel.read(hsInNetBuffer) < 0) {
+ if(engine.isInboundDone() && engine.isOutboundDone()) {
+ return false;
+ }
+ try {
+ engine.closeInbound();
+ } catch (SSLException e) {
+ Log.e("server-poc-test","Forced to close inbound. No proper SSL/TLS close notification message from peer");
+ }
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ hsInNetBuffer.flip();
+ try {
+ result = engine.unwrap(hsInNetBuffer, hsInAppBuffer);
+ hsInNetBuffer.compact();
+ handshakeStatus = result.getHandshakeStatus();
+ } catch (SSLException sslException) {
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ switch(result.getStatus()) {
+ case OK:
+ break;
+ case BUFFER_OVERFLOW:
+ hsInAppBuffer = enlargeApplicationBuffer(engine, hsInAppBuffer);
+ break;
+ case BUFFER_UNDERFLOW:
+ hsInNetBuffer = handleBufferUnderflow(engine, hsInNetBuffer);
+ break;
+ case CLOSED:
+ if (engine.isOutboundDone()) {
+ return false;
+ } else {
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+ }
+ case NEED_WRAP:
+ hsOutNetBuffer.clear();
+ try {
+ result = engine.wrap(hsOutAppBuffer, hsOutNetBuffer);
+ handshakeStatus = result.getHandshakeStatus();
+ } catch (SSLException sslException) {
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ switch(result.getStatus()) {
+ case OK:
+ hsOutNetBuffer.flip();
+ while(hsOutNetBuffer.hasRemaining()) {
+ socketChannel.write(hsOutNetBuffer);
+ }
+ break;
+ case BUFFER_OVERFLOW:
+ hsOutNetBuffer = enlargePacketBuffer(engine, hsOutNetBuffer);
+ break;
+ case BUFFER_UNDERFLOW:
+ throw new SSLException("Buffer underflow in handshake and wrap");
+ case CLOSED:
+ try {
+ hsOutNetBuffer.flip();
+ while(hsOutNetBuffer.hasRemaining()) {
+ socketChannel.write(hsOutNetBuffer);
+ }
+ hsInNetBuffer.clear();
+ } catch (Exception e) {
+ handshakeStatus = engine.getHandshakeStatus();
+ }
+ break;
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+ }
+ break;
+ case NEED_TASK:
+ Runnable task;
+ while((task = engine.getDelegatedTask()) != null) {
+ executor.execute(task);
+ }
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ case FINISHED:
+ break;
+ case NOT_HANDSHAKING:
+ break;
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + handshakeStatus);
+ }
+ }
+ return true;
+ }
+
+ public ByteBuffer enlargePacketBuffer(SSLEngine engine, ByteBuffer buffer) {
+ return enlargeBuffer(buffer, engine.getSession().getPacketBufferSize());
+ }
+
+ public ByteBuffer enlargeApplicationBuffer(SSLEngine engine, ByteBuffer buffer) {
+ return enlargeBuffer(buffer, engine.getSession().getApplicationBufferSize());
+ }
+
+ public ByteBuffer enlargeBuffer(ByteBuffer buffer, int bufferSize) {
+ if(bufferSize > buffer.capacity()) {
+ buffer = ByteBuffer.allocate(bufferSize);
+ }
+ else {
+ buffer = ByteBuffer.allocate(buffer.capacity() * 2);
+ }
+ return buffer;
+ }
+ public ByteBuffer handleBufferUnderflow(SSLEngine engine, ByteBuffer buffer) {
+ if(engine.getSession().getPacketBufferSize() < buffer.limit()) {
+ return buffer;
+ }
+ else {
+ ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer);
+ buffer.flip();
+ replaceBuffer.put(buffer);
+ return replaceBuffer;
+ }
+ }
+
+ public void read(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+ dataInNetBuffer.clear();
+ int bytesRead = socketChannel.read(dataInNetBuffer);
+ if(bytesRead > 0) {
+ dataInNetBuffer.flip();
+ while(dataInNetBuffer.hasRemaining()) {
+ dataInAppBuffer.clear();
+ SSLEngineResult result = engine.unwrap(dataInNetBuffer, dataInAppBuffer);
+ switch(result.getStatus()) {
+ case OK:
+ dataInAppBuffer.flip();
+ break;
+ case BUFFER_OVERFLOW:
+ dataInAppBuffer = enlargeApplicationBuffer(engine, dataInAppBuffer);
+ break;
+ case BUFFER_UNDERFLOW:
+ dataInNetBuffer = handleBufferUnderflow(engine, dataInNetBuffer);
+ break;
+ case CLOSED:
+ closeConnection(socketChannel, engine);
+ return;
+ default:
+ throw new IllegalStateException("invalid SSL status: " + result.getStatus());
+ }
+ }
+ }
+ else if(bytesRead < 0) {
+ handleEndOfStream(socketChannel, engine);
+ }
+ }
+
+ public void handleEndOfStream(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+ try {
+ engine.closeInbound();
+ }
+ catch (Exception e) {
+ Log.e("server-poc-test", "Close inbound forced");
+ }
+ closeConnection(socketChannel, engine);
+ }
+
+ public void closeConnection(SocketChannel socketChannel, SSLEngine engine) throws IOException {
+ try{
+ serverKey.close();
+ } catch (IOException e){
+ e.printStackTrace();
+ }
+
+ try {
+ trustedCert.close();
+ } catch (IOException e){
+ e.printStackTrace();
+ }
+
+ engine.closeOutbound();
+ doHandshake(socketChannel, engine);
+ socketChannel.close();
+ }
+}