Merge "Test Bitmap.getColorSpace()"
diff --git a/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py b/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
index f8a97e4..065f854 100644
--- a/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
+++ b/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
@@ -118,7 +118,7 @@
dist_max = math.sqrt(pow(w, 2)+pow(h, 2))/2
for spb_ct in SPB_CT_LIST:
# list sample block center location
- num_sample = (1-spb_ct*2)/spb_r/2 + 1
+ num_sample = int(numpy.asscalar((1-spb_ct*2)/spb_r/2 + 1))
ct_cord_x = numpy.concatenate(
(numpy.arange(spb_ct, 1-spb_ct+spb_r, spb_r*2),
spb_ct*numpy.ones((num_sample-1)),
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index ee92391..ef49b68 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -126,6 +126,8 @@
<meta-data android:name="test_category" android:value="@string/test_category_device_admin" />
<meta-data android:name="test_required_features"
android:value="android.software.device_admin" />
+ <meta-data android:name="test_excluded_features"
+ android:value="android.hardware.type.watch" />
</activity>
<activity android:name=".admin.ScreenLockTestActivity"
@@ -2968,6 +2970,7 @@
<activity android:name=".managedprovisioning.CompHelperActivity">
<intent-filter>
<action android:name="com.android.cts.verifier.managedprovisioning.COMP_SET_ALWAYS_ON_VPN" />
+ <action android:name="com.android.cts.verifier.managedprovisioning.COMP_SET_MAXIMUM_PASSWORD_ATTEMPTS" />
<category android:name="android.intent.category.DEFAULT"></category>
</intent-filter>
</activity>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 024f775..d734b2a 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2960,6 +2960,28 @@
</string>
<string name="enterprise_privacy_set_proxy">Set Proxy</string>
<string name="enterprise_privacy_clear_proxy">Clear Proxy</string>
+ <string name="enterprise_privacy_failed_password_wipe">Wipe on authentication failure</string>
+ <string name="enterprise_privacy_failed_password_wipe_info">
+ Please do the following:\n
+ 1) Press the Open Settings button.\n
+ 2) In the screen that opens, verify that you are not told all device data will be deleted if you mistype your password too many times.\n
+ 3) Use the Back button to return to this page.\n
+ 4) Press the Set Limit button to set the maximum number of password attempts.\n
+ 5) Repeat steps (1) through (3), verifying that in step (2), you are told now that all device data will be deleted if you mistype your password 100 times.\n
+ 6) Press the Finish button to clear the maximum number of password attempts.
+ </string>
+ <string name="enterprise_privacy_set_limit">Set Limit</string>
+ <string name="enterprise_privacy_comp_failed_password_wipe">Wipe on authentication failure (managed profile)</string>
+ <string name="enterprise_privacy_comp_failed_password_wipe_info">
+ Please do the following:\n
+ 1) Press the Start button to create a work profile.\n
+ 2) Press the Open Settings button.\n
+ 3) In the screen that opens, verify that you are not told work profile data will be deleted if you mistype your password too many times.\n
+ 4) Use the Back button to return to this page.\n
+ 5) Press the Set Limit button to set the maximum number of password attempts.\n
+ 6) Repeat steps (2) through (4), verifying that in step (3), you are told now that work profile device data will be deleted if you mistype your password 100 times.\n
+ 7) Press the Finish button to remove the work profile.
+ </string>
<string name="enterprise_privacy_quick_settings">Quick settings disclosure</string>
<string name="enterprise_privacy_quick_settings_info">
Please do the following:\n
diff --git a/apps/CtsVerifier/res/xml/device_admin_comp_profile.xml b/apps/CtsVerifier/res/xml/device_admin_comp_profile.xml
index 3e6b332..784258d 100644
--- a/apps/CtsVerifier/res/xml/device_admin_comp_profile.xml
+++ b/apps/CtsVerifier/res/xml/device_admin_comp_profile.xml
@@ -15,5 +15,10 @@
-->
<!-- BEGIN_INCLUDE(meta_data) -->
-<device-admin xmlns:android="http://schemas.android.com/apk/res/android"/>
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-policies>
+ <wipe-data />
+ <watch-login />
+ </uses-policies>
+</device-admin>
<!-- END_INCLUDE(meta_data) -->
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index cc1043d..466436f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -91,6 +91,10 @@
public static final String COMMAND_CLEAR_ALWAYS_ON_VPN = "clear-always-on-vpn";
public static final String COMMAND_SET_GLOBAL_HTTP_PROXY = "set-global-http-proxy";
public static final String COMMAND_CLEAR_GLOBAL_HTTP_PROXY = "clear-global-http-proxy";
+ public static final String COMMAND_SET_MAXIMUM_PASSWORD_ATTEMPTS =
+ "set-maximum-password-attempts";
+ public static final String COMMAND_CLEAR_MAXIMUM_PASSWORD_ATTEMPTS =
+ "clear-maximum-password-attempts";
public static final String EXTRA_USER_RESTRICTION =
"com.android.cts.verifier.managedprovisioning.extra.USER_RESTRICTION";
@@ -378,6 +382,18 @@
}
mDpm.setRecommendedGlobalProxy(mAdmin, null);
}
+ case COMMAND_SET_MAXIMUM_PASSWORD_ATTEMPTS: {
+ if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+ return;
+ }
+ mDpm.setMaximumFailedPasswordsForWipe(mAdmin, 100);
+ } break;
+ case COMMAND_CLEAR_MAXIMUM_PASSWORD_ATTEMPTS: {
+ if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+ return;
+ }
+ mDpm.setMaximumFailedPasswordsForWipe(mAdmin, 0);
+ }
}
} catch (Exception e) {
Log.e(TAG, "Failed to execute command: " + intent, e);
@@ -442,6 +458,7 @@
mDpm.clearPackagePersistentPreferredActivities(mAdmin, getPackageName());
mDpm.setAlwaysOnVpnPackage(mAdmin, null, false);
mDpm.setRecommendedGlobalProxy(mAdmin, null);
+ mDpm.setMaximumFailedPasswordsForWipe(mAdmin, 0);
uninstallHelperPackage();
removeManagedProfile();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompDeviceAdminTestReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompDeviceAdminTestReceiver.java
index 4f8b78a..bd43e8e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompDeviceAdminTestReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompDeviceAdminTestReceiver.java
@@ -45,6 +45,7 @@
// user to send us commands.
final IntentFilter filter = new IntentFilter();
filter.addAction(CompHelperActivity.ACTION_SET_ALWAYS_ON_VPN);
+ filter.addAction(CompHelperActivity.ACTION_SET_MAXIMUM_PASSWORD_ATTEMPTS);
dpm.addCrossProfileIntentFilter(getWho(context), filter,
DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompHelperActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompHelperActivity.java
index 3da0547..8d965a7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompHelperActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompHelperActivity.java
@@ -36,22 +36,27 @@
// Set always-on VPN.
public static final String ACTION_SET_ALWAYS_ON_VPN
= "com.android.cts.verifier.managedprovisioning.COMP_SET_ALWAYS_ON_VPN";
+ // Set the number of login failures after which the managed profile is wiped.
+ public static final String ACTION_SET_MAXIMUM_PASSWORD_ATTEMPTS
+ = "com.android.cts.verifier.managedprovisioning.COMP_SET_MAXIMUM_PASSWORD_ATTEMPTS";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ final ComponentName admin = CompDeviceAdminTestReceiver.getReceiverComponentName();
final DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
Context.DEVICE_POLICY_SERVICE);
final String action = getIntent().getAction();
if (ACTION_SET_ALWAYS_ON_VPN.equals(action)) {
try {
- dpm.setAlwaysOnVpnPackage(CompDeviceAdminTestReceiver.getReceiverComponentName(),
- getPackageName(), false /* lockdownEnabled */);
+ dpm.setAlwaysOnVpnPackage(admin, getPackageName(), false /* lockdownEnabled */);
} catch (Exception e) {
Log.e(TAG, "Unable to set always-on VPN", e);
}
+ } else if (ACTION_SET_MAXIMUM_PASSWORD_ATTEMPTS.equals(action)) {
+ dpm.setMaximumFailedPasswordsForWipe(admin, 100);
}
finish();
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
index 54a7ec7..1844f20 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
@@ -60,6 +60,10 @@
= "ENTERPRISE_PRIVACY_COMP_ALWAYS_ON_VPN";
private static final String ENTERPRISE_PRIVACY_GLOBAL_HTTP_PROXY
= "ENTERPRISE_PRIVACY_GLOBAL_HTTP_PROXY";
+ private static final String ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE
+ = "ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE";
+ private static final String ENTERPRISE_PRIVACY_COMP_FAILED_PASSWORD_WIPE
+ = "ENTERPRISE_PRIVACY_COMP_FAILED_PASSWORD_WIPE";
private static final String ENTERPRISE_PRIVACY_QUICK_SETTINGS
= "ENTERPRISE_PRIVACY_QUICK_SETTINGS";
private static final String ENTERPRISE_PRIVACY_KEYGUARD = "ENTERPRISE_PRIVACY_KEYGUARD";
@@ -68,14 +72,6 @@
public static final String EXTRA_TEST_ID =
"com.android.cts.verifier.managedprovisioning.extra.TEST_ID";
- private void setManagedProfileActivityEnabled(boolean enabled) {
- getPackageManager().setComponentEnabledSetting(
- new ComponentName(this, CompHelperActivity.class),
- enabled ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT :
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
- }
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -90,7 +86,10 @@
}
});
setTestListAdapter(adapter);
- setManagedProfileActivityEnabled(false);
+ getPackageManager().setComponentEnabledSetting(
+ new ComponentName(this, CompHelperActivity.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
}
private Intent buildCommandIntent(String command) {
@@ -219,6 +218,32 @@
new ButtonInfo(R.string.enterprise_privacy_clear_proxy,
buildCommandIntent(CommandReceiverActivity
.COMMAND_CLEAR_GLOBAL_HTTP_PROXY))}));
+ adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE,
+ R.string.enterprise_privacy_failed_password_wipe,
+ R.string.enterprise_privacy_failed_password_wipe_info,
+ new ButtonInfo[] {
+ new ButtonInfo(R.string.enterprise_privacy_open_settings,
+ new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
+ new ButtonInfo(R.string.enterprise_privacy_set_limit,
+ buildCommandIntent(CommandReceiverActivity
+ .COMMAND_SET_MAXIMUM_PASSWORD_ATTEMPTS)),
+ new ButtonInfo(R.string.enterprise_privacy_finish,
+ buildCommandIntent(CommandReceiverActivity
+ .COMMAND_CLEAR_MAXIMUM_PASSWORD_ATTEMPTS))}));
+ adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_COMP_FAILED_PASSWORD_WIPE,
+ R.string.enterprise_privacy_comp_failed_password_wipe,
+ R.string.enterprise_privacy_comp_failed_password_wipe_info,
+ new ButtonInfo[] {
+ new ButtonInfo(R.string.enterprise_privacy_start,
+ buildCommandIntent(
+ CommandReceiverActivity.COMMAND_CREATE_MANAGED_PROFILE)),
+ new ButtonInfo(R.string.enterprise_privacy_open_settings,
+ new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
+ new ButtonInfo(R.string.enterprise_privacy_set_limit, new Intent(
+ CompHelperActivity.ACTION_SET_MAXIMUM_PASSWORD_ATTEMPTS)),
+ new ButtonInfo(R.string.enterprise_privacy_finish,
+ buildCommandIntent(
+ CommandReceiverActivity.COMMAND_REMOVE_MANAGED_PROFILE))}));
adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_QUICK_SETTINGS,
R.string.enterprise_privacy_quick_settings,
R.string.enterprise_privacy_quick_settings_info,
@@ -274,6 +299,5 @@
PolicyTransparencyTestListActivity.MODE_DEVICE_OWNER);
startActivity(intent);
startActivity(buildCommandIntent(CommandReceiverActivity.COMMAND_REMOVE_MANAGED_PROFILE));
- setManagedProfileActivityEnabled(false);
}
}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
index 2036289..3321443 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
@@ -38,6 +38,7 @@
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.RunUtil;
import org.easymock.EasyMock;
import org.junit.After;
@@ -481,7 +482,8 @@
for (ShardThread thread : threads) {
thread.join(1000);
}
-
+ // Allow some time for ResultReport to finalize the results coming from the threads.
+ RunUtil.getDefault().sleep(200);
EasyMock.verify(mMockDevice, mMockBuildInfo);
// Check aggregated results to make sure it's consistent.
IInvocationResult result = mReporter.getResult();
@@ -530,7 +532,8 @@
for (ShardThread thread : threads) {
thread.join(1000);
}
-
+ // Allow some time for ResultReport to finalize the results coming from the threads.
+ RunUtil.getDefault().sleep(200);
EasyMock.verify(mMockDevice, mMockBuildInfo);
// Check aggregated results to make sure it's consistent.
IInvocationResult result = mReporter.getResult();
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
index 5a6794a..516e32e 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
@@ -24,6 +24,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+LOCAL_JAVA_LIBRARIES = conscrypt
+
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
index cf8a05d..962c5f5 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
@@ -24,6 +24,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+LOCAL_JAVA_LIBRARIES = conscrypt
+
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
index 5e083f7..1167a08 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
@@ -24,6 +24,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+LOCAL_JAVA_LIBRARIES = conscrypt
+
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
index cebdb68..3a6e65a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
@@ -24,9 +24,18 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
import android.security.KeyChainException;
import android.test.MoreAsserts;
+import com.android.org.conscrypt.TrustedCertificateStore;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -170,14 +179,18 @@
super.tearDown();
}
- public void testCaCertsOperations() throws InterruptedException {
- byte[] cert = TEST_CA.getBytes();
+ public void testCaCertsOperations() throws InterruptedException, CertificateException {
+ final byte[] cert = TEST_CA.getBytes();
+ final Certificate caCert = CertificateFactory.getInstance("X.509")
+ .generateCertificate(new ByteArrayInputStream(cert));
+ final TrustedCertificateStore store = new TrustedCertificateStore();
mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE);
assertEquals(CERT_INSTALLER_PACKAGE,
mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT));
// Exercise installCaCert()
+ assertNull(store.getCertificateAlias(caCert));
installCaCert(cert);
assertResult("installCaCert", true);
assertTrue("Certificate is not installed properly", mDpm.hasCaCertInstalled(
@@ -187,12 +200,21 @@
verifyCaCert(cert);
assertResult("getInstalledCaCerts()", true);
+ // Verify that the CA cert was marked as installed by the Device Owner / Profile Owner.
+ final String alias = store.getCertificateAlias(caCert);
+ assertNotNull(alias);
+ verifyOwnerInstalledStatus(alias, true);
+
// Exercise uninstallCaCert()
removeCaCert(cert);
assertResult("uninstallCaCert()", true);
assertFalse("Certificate is not removed properly", mDpm.hasCaCertInstalled(
ADMIN_RECEIVER_COMPONENT, cert));
+ // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
+ // Owner.
+ verifyOwnerInstalledStatus(alias, false);
+
// Clear delegated cert installer.
// Tests after this are expected to fail.
mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, null);
@@ -288,6 +310,13 @@
mContext.sendBroadcast(intent);
}
+ private void verifyOwnerInstalledStatus(String alias, boolean expectOwnerInstalled) {
+ final List<String> ownerInstalledCerts =
+ mDpm.getOwnerInstalledCaCerts(Process.myUserHandle());
+ assertNotNull(ownerInstalledCerts);
+ assertEquals(expectOwnerInstalled, ownerInstalledCerts.contains(alias));
+ }
+
private void assertResult(String testName, Boolean expectSuccess) throws InterruptedException {
assertTrue("Cert installer did not respond in time.",
mAvailableResultSemaphore.tryAcquire(10, TimeUnit.SECONDS));
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
index a2d7a60..4e9abd7 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -24,7 +24,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java
index a5e2768..ff51e9e 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java
@@ -20,17 +20,50 @@
import android.content.ContentResolver;
import android.content.Context;
import android.os.Process;
-import android.os.UserHandle;
import android.provider.Settings;
+import com.android.org.conscrypt.TrustedCertificateStore;
+
+import java.io.ByteArrayInputStream;
import java.lang.reflect.Method;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.List;
public class AdminActionBookkeepingTest extends BaseDeviceOwnerTest {
+ /*
+ * The CA cert below is the content of cacert.pem as generated by:
+ *
+ * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+ */
+ private static final String TEST_CA =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+ "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+ "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+ "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+ "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+ "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+ "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+ "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+ "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+ "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+ "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+ "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+ "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+ "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+ "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+ "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+ "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+ "wQ==\n" +
+ "-----END CERTIFICATE-----";
@Override
protected void tearDown() throws Exception {
mDevicePolicyManager.setSecurityLoggingEnabled(getWho(), false);
mDevicePolicyManager.setNetworkLoggingEnabled(getWho(), false);
+ mDevicePolicyManager.uninstallCaCert(getWho(), TEST_CA.getBytes());
super.tearDown();
}
@@ -134,24 +167,55 @@
}
/**
- * Test: It should be recored whether the Device Owner or the user set the default IME.
+ * Test: It should be recored whether the Device Owner or the user set the current IME.
*/
public void testIsDefaultInputMethodSet() throws Exception {
- final UserHandle userHandle = Process.myUserHandle();
final String setting = Settings.Secure.DEFAULT_INPUT_METHOD;
final ContentResolver resolver = getContext().getContentResolver();
final String ime = Settings.Secure.getString(resolver, setting);
Settings.Secure.putString(resolver, setting, "com.test.1");
Thread.sleep(500);
- assertFalse(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertFalse(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
mDevicePolicyManager.setSecureSetting(getWho(), setting, "com.test.2");
Thread.sleep(500);
- assertTrue(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertTrue(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
Settings.Secure.putString(resolver, setting, ime);
Thread.sleep(500);
- assertFalse(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertFalse(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
+ }
+
+ /**
+ * Test: It should be recored whether the Device Owner or the user installed a CA cert.
+ */
+ public void testGetPolicyInstalledCaCerts() throws Exception {
+ final byte[] rawCert = TEST_CA.getBytes();
+ final Certificate cert = CertificateFactory.getInstance("X.509")
+ .generateCertificate(new ByteArrayInputStream(rawCert));
+ final TrustedCertificateStore store = new TrustedCertificateStore();
+
+ // Install a CA cert.
+ assertNull(store.getCertificateAlias(cert));
+ assertTrue(mDevicePolicyManager.installCaCert(getWho(), rawCert));
+ final String alias = store.getCertificateAlias(cert);
+ assertNotNull(alias);
+
+ // Verify that the CA cert was marked as installed by the Device Owner.
+ verifyOwnerInstalledStatus(alias, true);
+
+ // Uninstall the CA cert.
+ mDevicePolicyManager.uninstallCaCert(getWho(), rawCert);
+
+ // Verify that the CA cert is no longer marked as installed by the Device Owner.
+ verifyOwnerInstalledStatus(alias, false);
+ }
+
+ private void verifyOwnerInstalledStatus(String alias, boolean expectOwnerInstalled) {
+ final List<String> ownerInstalledCerts =
+ mDevicePolicyManager.getOwnerInstalledCaCerts(Process.myUserHandle());
+ assertNotNull(ownerInstalledCerts);
+ assertEquals(expectOwnerInstalled, ownerInstalledCerts.contains(alias));
}
}
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
index 6b83df7..6f9ef5d 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
@@ -24,7 +24,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util ub-uiautomator
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AdminActionBookkeepingTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AdminActionBookkeepingTest.java
index 8bfa080..ba7919d 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AdminActionBookkeepingTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AdminActionBookkeepingTest.java
@@ -22,26 +22,99 @@
import android.os.UserHandle;
import android.provider.Settings;
+import com.android.org.conscrypt.TrustedCertificateStore;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.List;
+
public class AdminActionBookkeepingTest extends BaseProfileOwnerTest {
+ /*
+ * The CA cert below is the content of cacert.pem as generated by:
+ *
+ * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+ */
+ private static final String TEST_CA =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+ "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+ "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+ "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+ "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+ "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+ "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+ "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+ "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+ "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+ "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+ "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+ "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+ "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+ "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+ "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+ "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+ "wQ==\n" +
+ "-----END CERTIFICATE-----";
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDevicePolicyManager.uninstallCaCert(getWho(), TEST_CA.getBytes());
+
+ super.tearDown();
+ }
+
/**
- * Test: It should be recored whether the Profile Owner or the user set the default IME.
+ * Test: It should be recored whether the Profile Owner or the user set the current IME.
*/
public void testIsDefaultInputMethodSet() throws Exception {
- final UserHandle userHandle = Process.myUserHandle();
final String setting = Settings.Secure.DEFAULT_INPUT_METHOD;
final ContentResolver resolver = getContext().getContentResolver();
final String ime = Settings.Secure.getString(resolver, setting);
Settings.Secure.putString(resolver, setting, "com.test.1");
Thread.sleep(500);
- assertFalse(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertFalse(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
mDevicePolicyManager.setSecureSetting(getWho(), setting, "com.test.2");
Thread.sleep(500);
- assertTrue(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertTrue(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
Settings.Secure.putString(resolver, setting, ime);
Thread.sleep(500);
- assertFalse(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertFalse(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
+ }
+
+ /**
+ * Test: It should be recored whether the Profile Owner or the user installed a CA cert.
+ */
+ public void testGetPolicyInstalledCaCerts() throws Exception {
+ final byte[] rawCert = TEST_CA.getBytes();
+ final Certificate cert = CertificateFactory.getInstance("X.509")
+ .generateCertificate(new ByteArrayInputStream(rawCert));
+ final TrustedCertificateStore store = new TrustedCertificateStore();
+
+ // Install a CA cert.
+ assertNull(store.getCertificateAlias(cert));
+ assertTrue(mDevicePolicyManager.installCaCert(getWho(), rawCert));
+ final String alias = store.getCertificateAlias(cert);
+ assertNotNull(alias);
+
+ // Verify that the CA cert was marked as installed by the Profile Owner.
+ verifyOwnerInstalledStatus(alias, true);
+
+ // Uninstall the CA cert.
+ mDevicePolicyManager.uninstallCaCert(getWho(), rawCert);
+
+ // Verify that the CA cert is no longer marked as installed by the Profile Owner.
+ verifyOwnerInstalledStatus(alias, false);
+ }
+
+ private void verifyOwnerInstalledStatus(String alias, boolean expectOwnerInstalled) {
+ final List<String> ownerInstalledCerts =
+ mDevicePolicyManager.getOwnerInstalledCaCerts(Process.myUserHandle());
+ assertNotNull(ownerInstalledCerts);
+ assertEquals(expectOwnerInstalled, ownerInstalledCerts.contains(alias));
}
}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index 994dc62..f9c9d03 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -47,6 +47,8 @@
</activity>
<service android:name=".SimpleService" android:exported="true">
</service>
+ <service android:name=".SimpleService2" android:exported="true" android:process=":other">
+ </service>
<receiver android:name=".SimpleReceiver" android:exported="true">
</receiver>
<receiver android:name=".SimpleRemoteReceiver" android:process=":receiver"
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleService2.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleService2.java
new file mode 100644
index 0000000..f66078e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleService2.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.launcherapps.simpleapp;
+
+public class SimpleService2 extends SimpleService {
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index f7ba94f..6a68a59 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -58,6 +58,22 @@
private static final long TIMEOUT_USER_REMOVED_MILLIS = TimeUnit.SECONDS.toMillis(15);
private static final long WAIT_SAMPLE_INTERVAL_MILLIS = 200;
+ /**
+ * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
+ * command output from the device. At any time, if the shell command does not output anything
+ * for a period longer than defined timeout the Tradefed run terminates.
+ */
+ private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20);
+
+ /** instrumentation test runner argument key used for individual test timeout */
+ protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
+
+ /**
+ * Sets timeout (in milliseconds) that will be applied to each test. In the
+ * event of a test timeout it will log the results and proceed with executing the next test.
+ */
+ private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
+
// From the UserInfo class
protected static final int FLAG_PRIMARY = 0x00000001;
protected static final int FLAG_GUEST = 0x00000004;
@@ -283,6 +299,9 @@
RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
pkgName, RUNNER, getDevice().getIDevice());
+ testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ testRunner.addInstrumentationArg(
+ TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
if (testClassName != null && testMethodName != null) {
testRunner.setMethodName(testClassName, testMethodName);
} else if (testClassName != null) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 614343b..ba36fbf 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -398,6 +398,9 @@
/** Tests for the API helper class. */
public void testCurrentApiHelper() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CurrentApiHelperTest",
mProfileUserId);
}
diff --git a/hostsidetests/theme/Android.mk b/hostsidetests/theme/Android.mk
index 7875b4d..7d1d18c 100644
--- a/hostsidetests/theme/Android.mk
+++ b/hostsidetests/theme/Android.mk
@@ -18,7 +18,13 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_RESOURCE_DIRS := assets/
+# Special handling for pre-release builds where the SDK version has not been
+# updated, in which case we'll use the version codename (ex. "O").
+ifeq (REL,$(PLATFORM_VERSION_CODENAME))
+ LOCAL_JAVA_RESOURCE_DIRS := assets/$(PLATFORM_SDK_VERSION)/
+else
+ LOCAL_JAVA_RESOURCE_DIRS := assets/$(PLATFORM_VERSION_CODENAME)/
+endif
LOCAL_MODULE_TAGS := optional
diff --git a/hostsidetests/theme/README b/hostsidetests/theme/README
index 76902e4..07e7d26 100644
--- a/hostsidetests/theme/README
+++ b/hostsidetests/theme/README
@@ -39,8 +39,8 @@
adb devices
2. Image generation occurs on all devices in parallel. Resulting sets of
- reference images are saved in assets/<dpi>.zip and will overwrite
- any existing sets. Image generation may be started using:
+ reference images are saved in assets/<platform>/<dpi>.zip and will
+ overwrite any existing sets. Image generation may be started using:
./cts/hostsidetests/theme/generate_images.sh
diff --git a/hostsidetests/theme/android_device.py b/hostsidetests/theme/android_device.py
index 68f7404..5601cd1 100644
--- a/hostsidetests/theme/android_device.py
+++ b/hostsidetests/theme/android_device.py
@@ -31,6 +31,7 @@
def runAdbCommand(self, cmd):
self.waitForAdbDevice()
adbCmd = "adb -s %s %s" %(self._adbDevice, cmd)
+ print adbCmd
adbProcess = subprocess.Popen(adbCmd.split(" "), bufsize = -1, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
return adbProcess.communicate()
@@ -38,6 +39,7 @@
return self.runAdbCommand("shell " + cmd)
def waitForAdbDevice(self):
+ print "waitForAdbDevice"
os.system("adb -s %s wait-for-device" %self._adbDevice)
def waitForBootComplete(self, timeout = 240):
@@ -77,6 +79,12 @@
# uid. So only use name like com.android.xyz
return processName in names
+ def getVersionSdkInt(self):
+ return int(self.runShellCommand("getprop ro.build.version.sdk")[0])
+
+ def getVersionCodename(self):
+ return self.runShellCommand("getprop ro.build.version.codename")[0].strip()
+
def getDensity(self):
if "emulator" in self._adbDevice:
return int(self.runShellCommand("getprop qemu.sf.lcd_density")[0])
@@ -89,10 +97,12 @@
def getOrientation(self):
return int(self.runShellCommand("dumpsys | grep SurfaceOrientation")[0].split()[1])
- def getHWType(self):
- (output, err) = self.runShellCommand("dumpsys | grep android.hardware.type")
- output = output.strip()
- return output
+ # Running dumpsys on the emulator currently yields a SIGSEGV, so don't do it.
+ #
+ #def getHWType(self):
+ # (output, err) = self.runShellCommand("dumpsys | grep android.hardware.type")
+ # output = output.strip()
+ # return output
def runAdbDevices():
devices = subprocess.check_output(["adb", "devices"])
diff --git a/hostsidetests/theme/app/AndroidManifest.xml b/hostsidetests/theme/app/AndroidManifest.xml
index 2bfc2be..2a03db9 100755
--- a/hostsidetests/theme/app/AndroidManifest.xml
+++ b/hostsidetests/theme/app/AndroidManifest.xml
@@ -25,15 +25,16 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name=".ThemeDeviceActivity" android:screenOrientation="portrait">
+ <activity android:name=".ThemeDeviceActivity"
+ android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <activity android:name=".GenerateImagesActivity" android:screenOrientation="portrait"
- android:configChanges=
- "screenSize|smallestScreenSize|screenLayout|orientation"
+ <activity android:name=".GenerateImagesActivity"
+ android:screenOrientation="portrait"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:exported="true" />
</application>
diff --git a/hostsidetests/theme/assets/24/360dpi.zip b/hostsidetests/theme/assets/24/360dpi.zip
deleted file mode 100755
index 98782d5..0000000
--- a/hostsidetests/theme/assets/24/360dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/24/hdpi.zip b/hostsidetests/theme/assets/24/hdpi.zip
deleted file mode 100644
index d9dc466..0000000
--- a/hostsidetests/theme/assets/24/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/24/tvdpi.zip b/hostsidetests/theme/assets/24/tvdpi.zip
deleted file mode 100644
index b9ef65a..0000000
--- a/hostsidetests/theme/assets/24/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/420dpi.zip b/hostsidetests/theme/assets/420dpi.zip
deleted file mode 100644
index bf70d35..0000000
--- a/hostsidetests/theme/assets/420dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/560dpi.zip b/hostsidetests/theme/assets/560dpi.zip
deleted file mode 100644
index 74e2228..0000000
--- a/hostsidetests/theme/assets/560dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/O/360dpi.zip b/hostsidetests/theme/assets/O/360dpi.zip
new file mode 100644
index 0000000..8b5f7d9
--- /dev/null
+++ b/hostsidetests/theme/assets/O/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/O/420dpi.zip b/hostsidetests/theme/assets/O/420dpi.zip
new file mode 100644
index 0000000..bc887a0
--- /dev/null
+++ b/hostsidetests/theme/assets/O/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/O/560dpi.zip b/hostsidetests/theme/assets/O/560dpi.zip
new file mode 100644
index 0000000..cc33325
--- /dev/null
+++ b/hostsidetests/theme/assets/O/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/O/hdpi.zip b/hostsidetests/theme/assets/O/hdpi.zip
new file mode 100644
index 0000000..094f1e7
--- /dev/null
+++ b/hostsidetests/theme/assets/O/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/O/mdpi.zip b/hostsidetests/theme/assets/O/mdpi.zip
new file mode 100644
index 0000000..e1cfa65
--- /dev/null
+++ b/hostsidetests/theme/assets/O/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/O/xhdpi.zip b/hostsidetests/theme/assets/O/xhdpi.zip
new file mode 100644
index 0000000..5f39517
--- /dev/null
+++ b/hostsidetests/theme/assets/O/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/O/xxhdpi.zip b/hostsidetests/theme/assets/O/xxhdpi.zip
new file mode 100644
index 0000000..66b4a83
--- /dev/null
+++ b/hostsidetests/theme/assets/O/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/O/xxxhdpi.zip b/hostsidetests/theme/assets/O/xxxhdpi.zip
new file mode 100644
index 0000000..979fb28
--- /dev/null
+++ b/hostsidetests/theme/assets/O/xxxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/hdpi.zip b/hostsidetests/theme/assets/hdpi.zip
deleted file mode 100644
index 6cc2d50..0000000
--- a/hostsidetests/theme/assets/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/mdpi.zip b/hostsidetests/theme/assets/mdpi.zip
deleted file mode 100644
index a350ff6..0000000
--- a/hostsidetests/theme/assets/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/tvdpi.zip b/hostsidetests/theme/assets/tvdpi.zip
deleted file mode 100644
index 3071780..0000000
--- a/hostsidetests/theme/assets/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/xhdpi.zip b/hostsidetests/theme/assets/xhdpi.zip
deleted file mode 100644
index cceab80..0000000
--- a/hostsidetests/theme/assets/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/xxhdpi.zip b/hostsidetests/theme/assets/xxhdpi.zip
deleted file mode 100644
index 05f2b8d..0000000
--- a/hostsidetests/theme/assets/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/xxxhdpi.zip b/hostsidetests/theme/assets/xxxhdpi.zip
deleted file mode 100644
index f4e0713..0000000
--- a/hostsidetests/theme/assets/xxxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/run_theme_capture_device.py b/hostsidetests/theme/run_theme_capture_device.py
index 20aecf8..d37864f 100755
--- a/hostsidetests/theme/run_theme_capture_device.py
+++ b/hostsidetests/theme/run_theme_capture_device.py
@@ -96,16 +96,21 @@
print "Found device: " + deviceSerial
device = androidDevice(deviceSerial)
+ version = device.getVersionCodename()
+ if version == "REL":
+ version = str(device.getVersionSdkInt())
+
density = device.getDensity()
+
# Reference images generated for tv should not be categorized by density
# rather by tv type. This is because TV uses leanback-specific material
# themes.
- if device.getHWType() == "android.hardware.type.television":
- resName = "tvdpi"
- elif CTS_THEME_dict.has_key(density):
- resName = CTS_THEME_dict[density]
+ if CTS_THEME_dict.has_key(density):
+ densityBucket = CTS_THEME_dict[density]
else:
- resName = str(density) + "dpi"
+ densityBucket = str(density) + "dpi"
+
+ resName = os.path.join(version, densityBucket)
device.uninstallApk("android.theme.app")
diff --git a/hostsidetests/theme/src/android/theme/cts/ColorUtils.java b/hostsidetests/theme/src/android/theme/cts/ColorUtils.java
new file mode 100644
index 0000000..0bbc9c7
--- /dev/null
+++ b/hostsidetests/theme/src/android/theme/cts/ColorUtils.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.theme.cts;
+
+/**
+ * A set of color-related utility methods, building upon those available in {@code Color}.
+ */
+public class ColorUtils {
+
+ private static final double XYZ_WHITE_REFERENCE_X = 95.047;
+ private static final double XYZ_WHITE_REFERENCE_Y = 100;
+ private static final double XYZ_WHITE_REFERENCE_Z = 108.883;
+ private static final double XYZ_EPSILON = 0.008856;
+ private static final double XYZ_KAPPA = 903.3;
+
+ private ColorUtils() {}
+
+ /**
+ * Performs alpha blending of two colors using Porter-Duff SRC_OVER.
+ *
+ * @param src
+ * @param dst
+ */
+ public static int blendSrcOver(int src, int dst) {
+ int x = 255 - a(src);
+ int Ar = clamp(a(src) + a(dst) * x);
+ int Rr = clamp(r(src) + r(dst) * x);
+ int Gr = clamp(g(src) + g(dst) * x);
+ int Br = clamp(b(src) + b(dst) * x);
+ return argb(Ar, Rr, Gr, Br);
+ }
+
+ private static int clamp(int value) {
+ return value > 255 ? 255 : value < 0 ? 0 : value;
+ }
+
+ /**
+ * Return a color-int from alpha, red, green, blue components.
+ * These component values should be \([0..255]\), but there is no
+ * range check performed, so if they are out of range, the
+ * returned color is undefined.
+ *
+ * @param alpha Alpha component \([0..255]\) of the color
+ * @param red Red component \([0..255]\) of the color
+ * @param green Green component \([0..255]\) of the color
+ * @param blue Blue component \([0..255]\) of the color
+ */
+ public static int argb(int alpha, int red, int green, int blue) {
+ return (alpha << 24) | (red << 16) | (green << 8) | blue;
+ }
+
+ /**
+ * Return the alpha component of a color int. This is the same as saying
+ * color >>> 24
+ */
+ public static int a(int color) {
+ return color >>> 24;
+ }
+
+ /**
+ * Return the red component of a color int. This is the same as saying
+ * (color >> 16) & 0xFF
+ */
+ public static int r(int color) {
+ return (color >> 16) & 0xFF;
+ }
+
+ /**
+ * Return the green component of a color int. This is the same as saying
+ * (color >> 8) & 0xFF
+ */
+ public static int g(int color) {
+ return (color >> 8) & 0xFF;
+ }
+
+ /**
+ * Return the blue component of a color int. This is the same as saying
+ * color & 0xFF
+ */
+ public static int b(int color) {
+ return color & 0xFF;
+ }
+
+ /**
+ * Convert the ARGB color to its CIE Lab representative components.
+ *
+ * @param color the ARGB color to convert. The alpha component is ignored
+ * @param outLab 3-element array which holds the resulting LAB components
+ */
+ public static void colorToLAB(int color, double[] outLab) {
+ RGBToLAB(r(color), g(color), b(color), outLab);
+ }
+
+ /**
+ * Convert RGB components to its CIE Lab representative components.
+ *
+ * <ul>
+ * <li>outLab[0] is L [0 ...1)</li>
+ * <li>outLab[1] is a [-128...127)</li>
+ * <li>outLab[2] is b [-128...127)</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outLab 3-element array which holds the resulting LAB components
+ */
+ public static void RGBToLAB(int r, int g, int b, double[] outLab) {
+ // First we convert RGB to XYZ
+ RGBToXYZ(r, g, b, outLab);
+ // outLab now contains XYZ
+ XYZToLAB(outLab[0], outLab[1], outLab[2], outLab);
+ // outLab now contains LAB representation
+ }
+
+ /**
+ * Convert RGB components to its CIE XYZ representative components.
+ *
+ * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outXyz[0] is X [0 ...95.047)</li>
+ * <li>outXyz[1] is Y [0...100)</li>
+ * <li>outXyz[2] is Z [0...108.883)</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outXyz 3-element array which holds the resulting XYZ components
+ */
+ public static void RGBToXYZ(int r, int g, int b, double[] outXyz) {
+ if (outXyz.length != 3) {
+ throw new IllegalArgumentException("outXyz must have a length of 3.");
+ }
+
+ double sr = r / 255.0;
+ sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4);
+ double sg = g / 255.0;
+ sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4);
+ double sb = b / 255.0;
+ sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4);
+
+ outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
+ outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
+ outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
+ }
+
+ /**
+ * Converts a color from CIE XYZ to CIE Lab representation.
+ *
+ * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outLab[0] is L [0 ...1)</li>
+ * <li>outLab[1] is a [-128...127)</li>
+ * <li>outLab[2] is b [-128...127)</li>
+ * </ul>
+ *
+ * @param x X component value [0...95.047)
+ * @param y Y component value [0...100)
+ * @param z Z component value [0...108.883)
+ * @param outLab 3-element array which holds the resulting Lab components
+ */
+ public static void XYZToLAB(double x, double y, double z, double[] outLab) {
+ if (outLab.length != 3) {
+ throw new IllegalArgumentException("outLab must have a length of 3.");
+ }
+ x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X);
+ y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y);
+ z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z);
+ outLab[0] = Math.max(0, 116 * y - 16);
+ outLab[1] = 500 * (x - y);
+ outLab[2] = 200 * (y - z);
+ }
+
+ private static double pivotXyzComponent(double component) {
+ return component > XYZ_EPSILON
+ ? Math.pow(component, 1 / 3.0)
+ : (XYZ_KAPPA * component + 16) / 116;
+ }
+}
diff --git a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
index d9ed8a1..0f8768e 100755
--- a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
+++ b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
@@ -34,7 +34,11 @@
public class ComparisonTask implements Callable<File> {
private static final String TAG = "ComparisonTask";
- private static final int IMAGE_THRESHOLD = 2;
+ /** Maximum allowed LAB distance between two pixels. */
+ private static final double IMAGE_THRESHOLD = 0.76;
+
+ /** Neutral gray for blending colors. */
+ private static final int GRAY = 0xFF808080;
/** Maximum allowable number of consecutive failed pixels. */
private static final int MAX_CONSECUTIVE_FAILURES = 1;
@@ -90,30 +94,29 @@
return (color & 0xFF000000) >>> 24;
}
- private static boolean compare(BufferedImage reference, BufferedImage generated, int threshold) {
+ private static boolean compare(BufferedImage reference, BufferedImage generated,
+ double threshold) {
final int w = generated.getWidth();
final int h = generated.getHeight();
if (w != reference.getWidth() || h != reference.getHeight()) {
return false;
}
+ double maxDist = 0;
for (int i = 0; i < w; i++) {
int consecutive = 0;
for (int j = 0; j < h; j++) {
final int p1 = reference.getRGB(i, j);
final int p2 = generated.getRGB(i, j);
+ final double dist = computeLabDistance(p1, p2);
+ if (dist > threshold) {
+ System.err.println("fail " + dist);
- final int dr = getAlphaScaledRed(p1) - getAlphaScaledRed(p2);
- final int dg = getAlphaScaledGreen(p1) - getAlphaScaledGreen(p2);
- final int db = getAlphaScaledBlue(p1) - getAlphaScaledBlue(p2);
-
- if (Math.abs(db) > threshold ||
- Math.abs(dg) > threshold ||
- Math.abs(dr) > threshold) {
consecutive++;
if (consecutive > MAX_CONSECUTIVE_FAILURES) {
+ System.err.println("consecutive fail");
return false;
}
} else {
@@ -124,6 +127,30 @@
return true;
}
+ /**
+ * Returns the perceptual difference score (lower is better) for the
+ * provided ARGB pixels.
+ */
+ private static double computeLabDistance(int p1, int p2) {
+ // Blend with neutral gray to account for opacity.
+ p1 = ColorUtils.blendSrcOver(p1, GRAY);
+ p2 = ColorUtils.blendSrcOver(p2, GRAY);
+
+ // Convert to LAB.
+ double[] lab1 = new double[3];
+ double[] lab2 = new double[3];
+ ColorUtils.colorToLAB(p1, lab1);
+ ColorUtils.colorToLAB(p2, lab2);
+
+ // Compute the distance
+ double dist = 0;
+ for (int i = 0; i < 3; i++) {
+ double delta = lab1[i] - lab2[i];
+ dist += delta * delta;
+ }
+ return Math.sqrt(dist);
+ }
+
private static void createDiff(BufferedImage expected, BufferedImage actual, File out)
throws IOException {
final int w1 = expected.getWidth();
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index c87ff63..aa9d754 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -365,6 +365,7 @@
}
private static boolean checkHardwareTypeSkipTest(String hardwareTypeString) {
- return hardwareTypeString.contains("android.hardware.type.watch");
+ return hardwareTypeString.contains("android.hardware.type.watch")
+ || hardwareTypeString.contains("android.hardware.type.television");
}
}
diff --git a/tests/accessibility/res/values/strings.xml b/tests/accessibility/res/values/strings.xml
index 47ba12c..867f466 100644
--- a/tests/accessibility/res/values/strings.xml
+++ b/tests/accessibility/res/values/strings.xml
@@ -26,4 +26,7 @@
<!-- Description of the speaking accessibility service -->
<string name="some_description">Some description</string>
+ <!-- Summary of the speaking accessibility service -->
+ <string name="some_summary">Some summary</string>
+
</resources>
diff --git a/tests/accessibility/res/xml/speaking_accessibilityservice.xml b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
index c60fb54..5bc5a33 100644
--- a/tests/accessibility/res/xml/speaking_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
@@ -22,4 +22,5 @@
android:canRequestFilterKeyEvents="true"
android:canRequestEnhancedWebAccessibility="true"
android:settingsActivity="foo.bar.Activity"
- android:description="@string/some_description" />
+ android:description="@string/some_description"
+ android:summary="@string/some_summary" />
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
index bd14ee6..b90178a 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
@@ -79,6 +79,8 @@
assertEquals("foo.bar.Activity", speakingService.getSettingsActivityName());
assertEquals("Some description", speakingService.loadDescription(
getInstrumentation().getContext().getPackageManager()));
+ assertEquals("Some summary", speakingService.loadSummary(
+ getInstrumentation().getContext().getPackageManager()));
assertNotNull(speakingService.getResolveInfo());
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
index a933099..1761117 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
@@ -98,7 +98,6 @@
AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS));
assertEquals("FLAG_REQUEST_TOUCH_EXPLORATION_MODE", AccessibilityServiceInfo.flagToString(
AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE));
-
}
/**
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 6f8ed15..fd434b2 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -64,6 +64,7 @@
static final String SIMPLE_ACTIVITY_IMMEDIATE_EXIT = ".SimpleActivityImmediateExit";
static final String SIMPLE_ACTIVITY_CHAIN_EXIT = ".SimpleActivityChainExit";
static final String SIMPLE_SERVICE = ".SimpleService";
+ static final String SIMPLE_SERVICE2 = ".SimpleService2";
static final String SIMPLE_RECEIVER = ".SimpleReceiver";
static final String SIMPLE_REMOTE_RECEIVER = ".SimpleRemoteReceiver";
// The action sent back by the SIMPLE_APP after a restart.
@@ -579,11 +580,15 @@
}
public void testUidImportanceListener() throws Exception {
+ final Parcel data = Parcel.obtain();
Intent serviceIntent = new Intent();
- Parcel data = Parcel.obtain();
serviceIntent.setClassName(SIMPLE_PACKAGE_NAME,
SIMPLE_PACKAGE_NAME + SIMPLE_SERVICE);
ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, serviceIntent);
+ Intent service2Intent = new Intent();
+ service2Intent.setClassName(SIMPLE_PACKAGE_NAME,
+ SIMPLE_PACKAGE_NAME + SIMPLE_SERVICE2);
+ ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, service2Intent);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
@@ -621,17 +626,23 @@
UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid);
am.addOnUidImportanceListener(uidGoneListener,
- RunningAppProcessInfo.IMPORTANCE_EMPTY);
+ RunningAppProcessInfo.IMPORTANCE_CACHED);
- // First kill the process to start out in a stable state.
+ // First kill the processes to start out in a stable state.
conn.bind(WAIT_TIME);
+ conn2.bind(WAIT_TIME);
try {
conn.mService.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
}
+ try {
+ conn2.mService.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
+ } catch (RemoteException e) {
+ }
conn.unbind(WAIT_TIME);
+ conn2.unbind(WAIT_TIME);
- // Wait for uid's process to go away.
+ // Wait for uid's processes to go away.
uidGoneListener.waitForValue(RunningAppProcessInfo.IMPORTANCE_GONE,
RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
assertEquals(RunningAppProcessInfo.IMPORTANCE_GONE,
@@ -649,9 +660,9 @@
// Now unbind and see if we get told about it going to the background.
conn.unbind(WAIT_TIME);
- uidForegroundListener.waitForValue(RunningAppProcessInfo.IMPORTANCE_BACKGROUND,
- RunningAppProcessInfo.IMPORTANCE_EMPTY, WAIT_TIME);
- assertEquals(RunningAppProcessInfo.IMPORTANCE_BACKGROUND,
+ uidForegroundListener.waitForValue(RunningAppProcessInfo.IMPORTANCE_CACHED,
+ RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
+ assertEquals(RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Now kill the process and see if we are told about it being gone.
@@ -666,6 +677,54 @@
assertEquals(RunningAppProcessInfo.IMPORTANCE_GONE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
+ // Now we are going to try different combinations of binding to two processes to
+ // see if they are correctly combined together for the app.
+
+ // Bring up both services.
+ conn.bind(WAIT_TIME);
+ conn2.bind(WAIT_TIME);
+ uidForegroundListener.waitForValue(RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ RunningAppProcessInfo.IMPORTANCE_VISIBLE, WAIT_TIME);
+ assertEquals(RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ am.getPackageImportance(SIMPLE_PACKAGE_NAME));
+
+ // Bring down one service, app state should remain foreground.
+ conn2.unbind(WAIT_TIME);
+ assertEquals(RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ am.getPackageImportance(SIMPLE_PACKAGE_NAME));
+
+ // Bring down other service, app state should now be cached. (If the processes both
+ // actually get killed immediately, this is also not a correctly behaving system.)
+ conn.unbind(WAIT_TIME);
+ uidGoneListener.waitForValue(RunningAppProcessInfo.IMPORTANCE_CACHED,
+ RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
+ assertEquals(RunningAppProcessInfo.IMPORTANCE_CACHED,
+ am.getPackageImportance(SIMPLE_PACKAGE_NAME));
+
+ // Bring up one service, this should be sufficient to become foreground.
+ conn2.bind(WAIT_TIME);
+ uidForegroundListener.waitForValue(RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ RunningAppProcessInfo.IMPORTANCE_VISIBLE, WAIT_TIME);
+ assertEquals(RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ am.getPackageImportance(SIMPLE_PACKAGE_NAME));
+
+ // Bring up other service, should remain foreground.
+ conn.bind(WAIT_TIME);
+ assertEquals(RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ am.getPackageImportance(SIMPLE_PACKAGE_NAME));
+
+ // Bring down one service, should remain foreground.
+ conn.unbind(WAIT_TIME);
+ assertEquals(RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ am.getPackageImportance(SIMPLE_PACKAGE_NAME));
+
+ // And bringing down other service should put us back to cached.
+ conn2.unbind(WAIT_TIME);
+ uidGoneListener.waitForValue(RunningAppProcessInfo.IMPORTANCE_CACHED,
+ RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
+ assertEquals(RunningAppProcessInfo.IMPORTANCE_CACHED,
+ am.getPackageImportance(SIMPLE_PACKAGE_NAME));
+
data.recycle();
am.removeOnUidImportanceListener(uidForegroundListener);
@@ -673,8 +732,8 @@
}
public void testBackgroundCheckService() throws Exception {
+ final Parcel data = Parcel.obtain();
Intent serviceIntent = new Intent();
- Parcel data = Parcel.obtain();
serviceIntent.setClassName(SIMPLE_PACKAGE_NAME,
SIMPLE_PACKAGE_NAME + SIMPLE_SERVICE);
ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, serviceIntent);
diff --git a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
index 9d8f011..9871e05 100644
--- a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
@@ -37,6 +37,12 @@
NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
assertEquals("1", group.getId());
assertEquals("one", group.getName());
+ assertEquals(0, group.getNameResId());
+
+ NotificationChannelGroup group2 = new NotificationChannelGroup("2", 1234);
+ assertEquals("2", group2.getId());
+ assertEquals(null, group2.getName());
+ assertEquals(1234, group2.getNameResId());
}
public void testWriteToParcel() {
@@ -48,4 +54,19 @@
NotificationChannelGroup.CREATOR.createFromParcel(parcel);
assertEquals(group, fromParcel);
}
+
+ public void testClone() {
+ NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
+ NotificationChannelGroup cloned = group.clone();
+ assertEquals("1", cloned.getId());
+ assertEquals("one", cloned.getName());
+ assertEquals(0, cloned.getNameResId());
+
+ NotificationChannelGroup group2 = new NotificationChannelGroup("2", 1234);
+ NotificationChannelGroup cloned2 = group2.clone();
+ assertEquals("2", cloned2.getId());
+ assertEquals(null, cloned2.getName());
+ assertEquals(1234, cloned2.getNameResId());
+
+ }
}
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 2922238..c08d421 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -29,6 +29,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.support.test.InstrumentationRegistry;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -40,6 +41,7 @@
import android.app.stubs.R;
import com.android.compatibility.common.util.IBinderParcelable;
+import com.android.compatibility.common.util.SystemUtil;
import java.util.List;
@@ -57,8 +59,9 @@
private static final
String EXIST_CONN_TO_RECEIVE_SERVICE = "existing connection to receive service";
private static final String EXIST_CONN_TO_LOSE_SERVICE = "existing connection to lose service";
+ private static final String EXTERNAL_SERVICE_PACKAGE = "com.android.app2";
private static final String EXTERNAL_SERVICE_COMPONENT =
- "com.android.app2/android.app.stubs.LocalService";
+ EXTERNAL_SERVICE_PACKAGE + "/android.app.stubs.LocalService";
private int mExpectedServiceState;
private Context mContext;
private Intent mLocalService;
@@ -605,6 +608,10 @@
ActivityManager am = mContext.getSystemService(ActivityManager.class);
+ // Put target app on whitelist so we can start its services.
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd deviceidle whitelist +" + EXTERNAL_SERVICE_PACKAGE);
+
// No services should be reported back at the beginning
assertEquals(0, am.getRunningServices(maxReturnedServices).size());
try {
@@ -626,6 +633,8 @@
assertEquals(1, services.size());
assertEquals(android.os.Process.myUid(), services.get(0).uid);
} finally {
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd deviceidle whitelist -" + EXTERNAL_SERVICE_PACKAGE);
if (!success) {
mContext.stopService(mLocalService);
mContext.stopService(mExternalService);
diff --git a/tests/autofillservice/res/layout/nested_layout.xml b/tests/autofillservice/res/layout/nested_layout.xml
new file mode 100644
index 0000000..d81927a
--- /dev/null
+++ b/tests/autofillservice/res/layout/nested_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/container"
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <EditText android:id="@+id/field" android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/tests/autofillservice/res/layout/partially_manually_filled_activity.xml b/tests/autofillservice/res/layout/view_attribute_test_activity.xml
similarity index 74%
rename from tests/autofillservice/res/layout/partially_manually_filled_activity.xml
rename to tests/autofillservice/res/layout/view_attribute_test_activity.xml
index f7bfe4a..00f305d 100644
--- a/tests/autofillservice/res/layout/partially_manually_filled_activity.xml
+++ b/tests/autofillservice/res/layout/view_attribute_test_activity.xml
@@ -16,9 +16,8 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:orientation="vertical" android:id="@+id/rootContainer">
<EditText android:id="@+id/firstLevelDefault" android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@@ -48,4 +47,12 @@
android:layout_height="wrap_content" android:autoFillMode="manual" />
</LinearLayout>
+ <TextView android:id="@+id/textViewNoHint" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView android:id="@+id/textViewHintNone" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:autoFillHint="none" />
+ <TextView android:id="@+id/textViewPassword" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:autoFillHint="password" />
+ <TextView android:id="@+id/textViewPhoneName" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:autoFillHint="phone|username" />
</LinearLayout>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
new file mode 100644
index 0000000..652fae3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */package android.autofillservice.cts;
+
+import android.app.Activity;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for all activities in this test suite
+ */
+abstract class AbstractAutoFillActivity extends Activity {
+
+ /**
+ * Run an action in the UI thread, and blocks caller until the action is finished.
+ */
+ public final void syncRunOnUiThread(Runnable action) {
+ syncRunOnUiThread(action, Helper.UI_TIMEOUT_MS);
+ }
+
+ /**
+ * Run an action in the UI thread, and blocks caller until the action is finished or it times
+ * out.
+ */
+ public final void syncRunOnUiThread(Runnable action, int timeoutMs) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ runOnUiThread(() -> {
+ action.run();
+ latch.countDown();
+ });
+ try {
+ if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+ throw new AssertionError("action on UI thread timed out after " + timeoutMs + " ms");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted", e);
+ }
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
index ed9f01a..f530ad6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertWithMessage;
-import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
@@ -50,7 +49,7 @@
* <p>It's abstract because the sub-class must provide the view id, so it can support multiple
* UI types (like calendar and spinner).
*/
-abstract class AbstractDatePickerActivity extends Activity {
+abstract class AbstractDatePickerActivity extends AbstractAutoFillActivity {
private static final long OK_TIMEOUT_MS = 1000;
@@ -128,7 +127,7 @@
* Visits the {@code output} in the UiThread.
*/
void onOutput(ViewVisitor<EditText> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mOutput);
});
}
@@ -137,7 +136,7 @@
* Sets the date in the {@link DatePicker}.
*/
void setDate(int year, int month, int day) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
mDatePicker.updateDate(year, month, day);
});
}
@@ -147,7 +146,7 @@
*/
void tapOk() throws Exception {
mOkLatch = new CountDownLatch(1);
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
mOk.performClick();
});
boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
index 92d67e5..9fc4943 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
@@ -17,7 +17,6 @@
import static com.google.common.truth.Truth.assertWithMessage;
-import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
@@ -39,7 +38,7 @@
* <p>It's abstract because the sub-class must provide the view id, so it can support multiple
* UI types (like clock and spinner).
*/
-abstract class AbstractTimePickerActivity extends Activity {
+abstract class AbstractTimePickerActivity extends AbstractAutoFillActivity {
private static final long OK_TIMEOUT_MS = 1000;
@@ -117,7 +116,7 @@
* Visits the {@code output} in the UiThread.
*/
void onOutput(ViewVisitor<EditText> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mOutput);
});
}
@@ -126,7 +125,7 @@
* Sets the time in the {@link TimePicker}.
*/
void setTime(int hour, int minute) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
mTimePicker.setHour(hour);
mTimePicker.setMinute(minute);
});
@@ -137,7 +136,7 @@
*/
void tapOk() throws Exception {
mOkLatch = new CountDownLatch(1);
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
mOk.performClick();
});
boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
index c5ee4d2..e060d3a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
@@ -16,7 +16,6 @@
package android.autofillservice.cts;
-import android.app.Activity;
import android.app.assist.AssistStructure;
import android.content.Intent;
import android.os.Bundle;
@@ -28,7 +27,7 @@
/**
* This class simulates authentication at the dataset at reponse level
*/
-public class AuthenticationActivity extends Activity {
+public class AuthenticationActivity extends AbstractAutoFillActivity {
private static CannedFillResponse sResponse;
private static CannedFillResponse.CannedDataset sDataset;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index bfe081e..a3c4026 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -16,7 +16,6 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.IGNORE_DANGLING_SESSIONS;
import static android.autofillservice.cts.Helper.UI_TIMEOUT_MS;
import static android.autofillservice.cts.Helper.runShellCommand;
import static android.provider.Settings.Secure.AUTO_FILL_SERVICE;
@@ -28,6 +27,8 @@
import android.support.test.runner.AndroidJUnit4;
import android.widget.RemoteViews;
+
+import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -46,10 +47,17 @@
protected static UiBot sUiBot;
@BeforeClass
+ public static void removeLockScreen() {
+ runShellCommand("input keyevent KEYCODE_WAKEUP");
+ runShellCommand("input keyevent 82");
+ }
+
+ @BeforeClass
public static void setUiBot() throws Exception {
sUiBot = new UiBot(InstrumentationRegistry.getInstrumentation(), UI_TIMEOUT_MS);
}
+ @BeforeClass
@AfterClass
public static void disableService() {
runShellCommand("settings delete secure %s", AUTO_FILL_SERVICE);
@@ -88,12 +96,18 @@
* Asserts that there is no session left in the service.
*/
protected void assertNoDanglingSessions() {
- if (IGNORE_DANGLING_SESSIONS) return;
final String command = "cmd autofill list sessions";
final String result = runShellCommand(command);
assertWithMessage("Dangling sessions ('%s'): %s'", command, result).that(result).isEmpty();
}
+ /**
+ * Destroys all sessions.
+ */
+ protected void destroyAllSessions() {
+ runShellCommand("cmd autofill destroy sessions");
+ }
+
protected static Context getContext() {
return InstrumentationRegistry.getInstrumentation().getContext();
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index aac2c0c..f822568 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -63,6 +63,8 @@
final Bundle extras;
final RemoteViews presentation;
final IntentSender authentication;
+ final CharSequence negativeActionLabel;
+ final IntentSender negativeActionListener;
private CannedFillResponse(Builder builder) {
datasets = builder.mDatasets;
@@ -72,6 +74,8 @@
extras = builder.mExtras;
presentation = builder.mPresentation;
authentication = builder.mAuthentication;
+ negativeActionLabel = builder.mNegativeActionLabel;
+ negativeActionListener = builder.mNegativeActionListener;
}
/**
@@ -101,6 +105,9 @@
final AutoFillId id = node.getAutoFillId();
saveInfo.addSavableIds(id);
}
+ if (negativeActionLabel != null) {
+ saveInfo.setNegativeAction(negativeActionLabel, negativeActionListener);
+ }
builder.setSaveInfo(saveInfo.build());
}
return builder
@@ -115,7 +122,7 @@
+ ", savableIds=" + Arrays.toString(savableIds)
+ ", saveDescription=" + saveDescription
+ ", hasPresentation=" + (presentation != null)
- + ", hasPAuthentication=" + (authentication != null)
+ + ", hasAuthentication=" + (authentication != null)
+ "]";
}
@@ -127,6 +134,8 @@
private Bundle mExtras;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
+ private CharSequence mNegativeActionLabel;
+ private IntentSender mNegativeActionListener;
public Builder addDataset(CannedDataset dataset) {
mDatasets.add(dataset);
@@ -181,6 +190,16 @@
return this;
}
+ /**
+ * Sets the negative action spec.
+ */
+ public Builder setNegativeAction(CharSequence label,
+ IntentSender listener) {
+ mNegativeActionLabel = label;
+ mNegativeActionListener = listener;
+ return this;
+ }
+
public CannedFillResponse build() {
return new CannedFillResponse(this);
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
index a76929d..bb01487 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertWithMessage;
-import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
@@ -47,7 +46,7 @@
* <li>Buy Button
* </ul>
*/
-public class CheckoutActivity extends Activity {
+public class CheckoutActivity extends AbstractAutoFillActivity {
private static final long BUY_TIMEOUT_MS = 1000;
static final String ID_CC_NUMBER = "cc_number";
@@ -160,7 +159,7 @@
* Visits the {@code ccNumber} in the UiThread.
*/
void onCcNumber(ViewVisitor<EditText> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mCcNumber);
});
}
@@ -169,7 +168,7 @@
* Visits the {@code ccExpirationDate} in the UiThread.
*/
void onCcExpiration(ViewVisitor<Spinner> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mCcExpiration);
});
}
@@ -178,7 +177,7 @@
* Visits the {@code address} in the UiThread.
*/
void onAddress(ViewVisitor<RadioGroup> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mAddress);
});
}
@@ -187,7 +186,7 @@
* Visits the {@code homeAddress} in the UiThread.
*/
void onHomeAddress(ViewVisitor<RadioButton> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mHomeAddress);
});
}
@@ -196,7 +195,7 @@
* Visits the {@code saveCC} in the UiThread.
*/
void onSaveCc(ViewVisitor<CheckBox> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mSaveCc);
});
}
@@ -206,7 +205,7 @@
*/
void tapBuy() throws Exception {
mBuyLatch = new CountDownLatch(1);
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
mBuyButton.performClick();
});
boolean called = mBuyLatch.await(BUY_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
index 8922b78..91d1d43 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
@@ -42,12 +42,12 @@
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
import android.autofillservice.cts.InstrumentedAutoFillService.Replier;
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.view.autofill.AutoFillType;
import android.view.autofill.AutoFillValue;
import android.widget.Spinner;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -55,7 +55,6 @@
/**
* Test case for an activity containing non-TextField views.
*/
-@SmallTest
public class CheckoutActivityTest extends AutoFillServiceTestCase {
@Rule
@@ -69,6 +68,11 @@
mCheckoutActivity = mActivityRule.getActivity();
}
+ @After
+ public void finishWelcomeActivity() {
+ WelcomeActivity.finishIt();
+ }
+
@Test
public void testAutoFill() throws Exception {
// Set service.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
index 46164f3..a51a00c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
@@ -15,12 +15,10 @@
*/
package android.autofillservice.cts;
-import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import org.junit.Rule;
-@SmallTest
public class DatePickerCalendarActivityTest extends DatePickerTestCase<DatePickerCalendarActivity> {
@Rule
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
index 60d54b7..10851cd 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
@@ -15,12 +15,10 @@
*/
package android.autofillservice.cts;
-import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import org.junit.Rule;
-@SmallTest
public class DatePickerSpinnerActivityTest extends DatePickerTestCase<DatePickerSpinnerActivity> {
@Rule
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
index a99e4c9..b9aecb5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
@@ -33,6 +33,7 @@
import android.icu.util.Calendar;
import android.view.autofill.AutoFillValue;
+import org.junit.After;
import org.junit.Test;
/**
@@ -43,6 +44,11 @@
protected abstract T getDatePickerActivity();
+ @After
+ public void finishWelcomeActivity() {
+ WelcomeActivity.finishIt();
+ }
+
@Test
public void testAutoFillAndSave() throws Exception {
final T activity = getDatePickerActivity();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index f3a9f39..dd99b6d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -29,6 +29,7 @@
import android.service.autofill.FillResponse;
import android.support.test.InstrumentationRegistry;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Log;
import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillValue;
@@ -51,6 +52,11 @@
static final long CONNECTION_TIMEOUT_MS = 2000;
/**
+ * Timeout (in milliseconds) until framework unbinds from a service.
+ */
+ static final long IDLE_UNBIND_TIMEOUT_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+
+ /**
* Timeout (in milliseconds) for expected auto-fill requests.
*/
static final long FILL_TIMEOUT_MS = 2000;
@@ -65,9 +71,6 @@
*/
static final int UI_TIMEOUT_MS = 2000;
- // TODO(b/33197203 , b/35395043): temporary guard to skip assertions known to fail
- static final boolean IGNORE_DANGLING_SESSIONS = true;
-
/**
* Runs a Shell command, returning a trimmed response.
*/
@@ -253,7 +256,7 @@
cal.setTimeInMillis(value.getDateValue());
assertWithMessage("Wrong hour on AutoFillValue %s", value)
- .that(cal.get(Calendar.HOUR)).isEqualTo(hour);
+ .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour);
assertWithMessage("Wrong minute on AutoFillValue %s", value)
.that(cal.get(Calendar.MINUTE)).isEqualTo(minute);
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
index 33becf8..5d0c19a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
@@ -39,7 +39,6 @@
/**
* Test case for an activity containing non-TextField views with initial values set on XML.
*/
-@SmallTest
public class InitializedCheckoutActivityTest extends AutoFillServiceTestCase {
@Rule
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index e76fd67..63780cf 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -18,15 +18,13 @@
import static android.autofillservice.cts.Helper.CONNECTION_TIMEOUT_MS;
import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.IGNORE_DANGLING_SESSIONS;
import static android.autofillservice.cts.Helper.SAVE_TIMEOUT_MS;
+import static android.autofillservice.cts.Helper.IDLE_UNBIND_TIMEOUT_MS;
import static android.autofillservice.cts.Helper.dumpStructure;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static com.google.common.truth.Truth.assertWithMessage;
import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -36,12 +34,8 @@
import android.service.autofill.FillResponse;
import android.service.autofill.SaveCallback;
import android.util.Log;
-import android.view.autofill.AutoFillId;
-import android.view.autofill.AutoFillValue;
import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@@ -61,12 +55,21 @@
private static final String STATE_CONNECTED = "CONNECTED";
private static final String STATE_DISCONNECTED = "DISCONNECTED";
+ private static final AtomicReference<InstrumentedAutoFillService> sInstance =
+ new AtomicReference<>();
private static final AtomicReference<Replier> sReplier = new AtomicReference<>();
private static final BlockingQueue<String> sConnectionStates = new LinkedBlockingQueue<>();
+ public InstrumentedAutoFillService() {
+ sInstance.set(this);
+ }
+
+ public static AutoFillService peekInstance() {
+ return sInstance.get();
+ }
+
// TODO(b/33197203, b/33802548): add tests for onConnected() / onDisconnected() and/or remove
// overriden methods below that are only logging their calls.
-
@Override
public void onConnected() {
Log.v(TAG, "onConnected(): " + sConnectionStates);
@@ -113,11 +116,10 @@
* Waits until {@link #onDisconnected()} is called, or fails if it times out.
*/
static void waitUntilDisconnected() throws InterruptedException {
- if (IGNORE_DANGLING_SESSIONS) return;
-
- final String state = sConnectionStates.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ final String state = sConnectionStates.poll(2 * IDLE_UNBIND_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
if (state == null) {
- throw new AssertionError("not disconnected inin " + CONNECTION_TIMEOUT_MS + " ms");
+ throw new AssertionError("not disconnected in " + IDLE_UNBIND_TIMEOUT_MS + " ms");
}
assertWithMessage("Invalid connection state").that(state).isEqualTo(STATE_DISCONNECTED);
}
@@ -273,6 +275,7 @@
private void onSaveRequest(AssistStructure structure, Bundle data, SaveCallback callback) {
Log.d(TAG, "onSaveRequest()");
mSaveRequests.offer(new SaveRequest(structure, data, callback));
+ callback.onSuccess();
}
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index 6732870..3f12e27 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -17,7 +17,6 @@
import static com.google.common.truth.Truth.assertWithMessage;
-import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
@@ -40,7 +39,7 @@
* <li>Login Button
* </ul>
*/
-public class LoginActivity extends Activity {
+public class LoginActivity extends AbstractAutoFillActivity {
private static final String TAG = "LoginActivity";
private static String WELCOME_TEMPLATE = "Welcome to the new activity, %s!";
@@ -166,7 +165,7 @@
* Visits the {@code username_label} in the UiThread.
*/
void onUsernameLabel(ViewVisitor<TextView> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mUsernameLabel);
});
}
@@ -175,7 +174,7 @@
* Visits the {@code username} in the UiThread.
*/
void onUsername(ViewVisitor<EditText> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mUsernameEditText);
});
}
@@ -184,7 +183,7 @@
* Visits the {@code password_label} in the UiThread.
*/
void onPasswordLabel(ViewVisitor<TextView> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mPasswordLabel);
});
}
@@ -193,7 +192,7 @@
* Visits the {@code password} in the UiThread.
*/
void onPassword(ViewVisitor<EditText> v) {
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
v.visit(mPasswordEditText);
});
}
@@ -203,7 +202,7 @@
*/
String tapLogin() throws Exception {
mLoginLatch = new CountDownLatch(1);
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
mLoginButton.performClick();
});
boolean called = mLoginLatch.await(LOGIN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -217,7 +216,7 @@
*/
void setFlags(int flags) {
Log.d(TAG, "setFlags():" + flags);
- runOnUiThread(() -> {
+ syncRunOnUiThread(() -> {
getWindow().setFlags(flags, flags);
});
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 5514d7a..e812a4f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -19,6 +19,7 @@
import static android.autofillservice.cts.Helper.assertTextIsSanitized;
import static android.autofillservice.cts.Helper.assertTextOnly;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.Helper.runShellCommand;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
@@ -27,6 +28,7 @@
import static android.autofillservice.cts.LoginActivity.ID_USERNAME;
import static android.autofillservice.cts.LoginActivity.ID_USERNAME_LABEL;
import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
+import static android.provider.Settings.Secure.AUTO_FILL_SERVICE;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
@@ -43,18 +45,25 @@
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
import android.autofillservice.cts.InstrumentedAutoFillService.Replier;
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.IntentSender;
import android.os.Bundle;
-import android.support.test.filters.SmallTest;
+import android.os.SystemClock;
import android.support.test.rule.ActivityTestRule;
import android.support.test.uiautomator.UiObject2;
import android.view.autofill.AutoFillValue;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
/**
* This is the test case covering most scenarios - other test cases will cover characteristics
* specific to that test's activity (for example, custom views).
@@ -67,25 +76,29 @@
* Save
* - test cases where only non-savable-ids are changed
* - test case where 'no thanks' is tapped
- * - make sure snack bar times out (will require a shell cmd to change timeout)
*
* Other assertions
* - illegal state thrown on callback calls
* - system server state after calls (for example, no pending callback)
* - make sure there is no dangling session using 'cmd autofill list sessions'
*/
-@SmallTest
public class LoginActivityTest extends AutoFillServiceTestCase {
@Rule
public final ActivityTestRule<LoginActivity> mActivityRule =
- new ActivityTestRule<LoginActivity>(LoginActivity.class);
+ new ActivityTestRule<LoginActivity>(LoginActivity.class);
private LoginActivity mLoginActivity;
@Before
public void setActivity() {
mLoginActivity = mActivityRule.getActivity();
+ destroyAllSessions();
+ }
+
+ @After
+ public void finishWelcomeActivity() {
+ WelcomeActivity.finishIt();
}
@Test
@@ -264,11 +277,11 @@
replier.assertNumberUnhandledFillRequests(1);
replier.assertNumberUnhandledSaveRequests(0);
- // Sanity check: once saved, the session should be finsihed.
- assertNoDanglingSessions();
-
// Other sanity checks.
waitUntilDisconnected();
+
+ // Sanity check: once saved, the session should be finished.
+ assertNoDanglingSessions();
}
@Test
@@ -397,11 +410,70 @@
replier.assertNumberUnhandledFillRequests(0);
replier.assertNumberUnhandledSaveRequests(0);
- // Sanity check: once saved, the session should be finsihed.
- assertNoDanglingSessions();
-
// Other sanity checks.
waitUntilDisconnected();
+
+ // Sanity check: once saved, the session should be finsihed.
+ assertNoDanglingSessions();
+ }
+
+ private void setSnackBarLifetimeMs(int timeout) {
+ runShellCommand("cmd autofill set save_timeout %s", timeout);
+ }
+
+ @Test
+ public void testSaveSnackBarGoesAway() throws Exception {
+ enableService();
+ final int timeout = 1000;
+ setSnackBarLifetimeMs(timeout);
+
+ try {
+ // Set service.
+ final Replier replier = new Replier();
+ InstrumentedAutoFillService.setReplier(replier);
+
+
+ // Set expectations.
+ replier.addResponse(new CannedFillResponse.Builder()
+ .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .build());
+
+ // Trigger auto-fill.
+ mLoginActivity.onUsername((v) -> { v.requestFocus(); });
+ waitUntilConnected();
+
+ // Sanity check.
+ sUiBot.assertNoDatasets();
+
+ // Wait for onFill() before proceeding, otherwise the fields might be changed before
+ // the session started
+ replier.getNextFillRequest();
+
+ // Set credentials...
+ mLoginActivity.onUsername((v) -> { v.setText("malkovich"); });
+ mLoginActivity.onPassword((v) -> { v.setText("malkovich"); });
+
+ // ...and login
+ final String expectedMessage = getWelcomeMessage("malkovich");
+ final String actualMessage = mLoginActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+ InstrumentedAutoFillService.setReplier(replier); // Replier was reset onFill()
+
+ // Assert the snack bar is shown.
+ sUiBot.assertSaveShowing();
+ SystemClock.sleep(timeout);
+ sUiBot.assertSaveNotShowing();
+
+
+ // Sanity check: once timed out, session should be finsihed.
+ assertNoDanglingSessions();
+
+ // Other sanity checks.
+ waitUntilDisconnected();
+ } finally {
+ setSnackBarLifetimeMs(5000);
+ }
}
@Test
@@ -599,6 +671,7 @@
// Trigger auto-fill.
mLoginActivity.onUsername((v) -> { v.requestFocus(); });
+ waitUntilConnected();
// Assert sanitization on fill request:
final FillRequest fillRequest = replier.getNextFillRequest();
@@ -628,6 +701,113 @@
"DA PASSWORD");
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
+
+ // Sanity checks.
+ waitUntilDisconnected();
+ }
+
+ @Test
+ public void testDisableSelfWhenConnected() throws Exception {
+ enableService();
+
+ // Ensure enabled.
+ assertServiceEnabled();
+
+ // Set no-op behavior.
+ final Replier replier = new Replier();
+ replier.addResponse(new CannedFillResponse.Builder()
+ .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .build());
+ InstrumentedAutoFillService.setReplier(replier);
+
+ // Trigger auto-fill.
+ mLoginActivity.onUsername((v) -> v.requestFocus());
+ waitUntilConnected();
+
+ // Can disable while connected.
+ mLoginActivity.runOnUiThread(() ->
+ InstrumentedAutoFillService.peekInstance().disableSelf());
+
+ // Ensure disabled.
+ assertServiceDisabled();
+ }
+
+ @Test
+ public void testDisableSelfWhenDisconnected() throws Exception {
+ enableService();
+
+ // Ensure enabled.
+ assertServiceEnabled();
+
+ // Set no-op behavior.
+ final Replier replier = new Replier();
+ replier.addResponse(new CannedFillResponse.Builder()
+ .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .build());
+ InstrumentedAutoFillService.setReplier(replier);
+
+ // Trigger auto-fill.
+ mLoginActivity.onUsername((v) -> v.requestFocus());
+ waitUntilConnected();
+
+ // Wait until we timeout and disconnect.
+ waitUntilDisconnected();
+
+ // Cannot disable while disconnected.
+ mLoginActivity.runOnUiThread(() ->
+ InstrumentedAutoFillService.peekInstance().disableSelf());
+
+ // Ensure enabled.
+ assertServiceEnabled();
+ }
+
+ @Test
+ public void testCustomNegativeSaveButton() throws Exception {
+ enableService();
+
+ // Set service behavior.
+ final Replier replier = new Replier();
+
+ final String intentAction = "android.autofillservice.cts.CUSTOM_ACTION";
+
+ // Configure the save UI.
+ final IntentSender listener = PendingIntent.getBroadcast(
+ getContext(), 0, new Intent(intentAction), 0).getIntentSender();
+
+ replier.addResponse(new CannedFillResponse.Builder()
+ .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .setNegativeAction("Foo", listener)
+ .build());
+ InstrumentedAutoFillService.setReplier(replier);
+
+ // Trigger auto-fill.
+ mLoginActivity.onUsername((v) -> v.requestFocus());
+ waitUntilConnected();
+
+ // Wait for onFill() before proceeding.
+ replier.getNextFillRequest();
+
+ // Trigger save.
+ mLoginActivity.onUsername((v) -> v.setText("foo"));
+ mLoginActivity.onPassword((v) -> v.setText("foo"));
+ mLoginActivity.tapLogin();
+
+ // Start watching for the negative intent
+ final CountDownLatch latch = new CountDownLatch(1);
+ final IntentFilter intentFilter = new IntentFilter(intentAction);
+ getContext().registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ getContext().unregisterReceiver(this);
+ latch.countDown();
+ }
+ }, intentFilter);
+
+ // Trigger the negative button.
+ sUiBot.saveForAutofill(false);
+
+ // Wait for the custom action.
+ assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
}
@Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
index c8ab9d8..d8651b6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
@@ -15,12 +15,10 @@
*/
package android.autofillservice.cts;
-import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import org.junit.Rule;
-@SmallTest
public class TimePickerClockActivityTest extends TimePickerTestCase<TimePickerClockActivity> {
@Rule
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
index 471ec61..ab86255 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
@@ -15,12 +15,10 @@
*/
package android.autofillservice.cts;
-import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import org.junit.Rule;
-@SmallTest
public class TimePickerSpinnerActivityTest extends TimePickerTestCase<TimePickerSpinnerActivity> {
@Rule
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
index 2c5a434..0ba1c4b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
@@ -34,6 +34,7 @@
import android.icu.util.Calendar;
import android.view.autofill.AutoFillValue;
+import org.junit.After;
import org.junit.Test;
/**
@@ -44,6 +45,11 @@
protected abstract T getTimePickerActivity();
+ @After
+ public void finishWelcomeActivity() {
+ WelcomeActivity.finishIt();
+ }
+
@Test
public void testAutoFillAndSave() throws Exception {
final T activity = getTimePickerActivity();
@@ -55,7 +61,7 @@
// Set expectations.
final Calendar cal = Calendar.getInstance();
- cal.set(Calendar.HOUR, 4);
+ cal.set(Calendar.HOUR_OF_DAY, 4);
cal.set(Calendar.MINUTE, 20);
replier.addResponse(new CannedDataset.Builder()
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index cce0dda..a2ceafb 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -24,6 +24,7 @@
import android.app.Instrumentation;
import android.content.Context;
import android.content.res.Resources;
+import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
@@ -113,6 +114,20 @@
return assertSaveShowing(SAVE_DATA_TYPE_GENERIC, null);
}
+ /**
+ * Asserts the save snackbar is not showing and returns it.
+ */
+ void assertSaveNotShowing() {
+ try {
+ assertSaveShowing();
+ } catch (Throwable t) {
+ // TODO(b/33197203): use a more elegant check than catching the expection because it's
+ // not showing...
+ return;
+ }
+ throw new AssertionError("snack bar is showing");
+ }
+
UiObject2 assertSaveShowing(int type, String description) {
final UiObject2 snackbar = waitForObject(By.res("android", RESOURCE_ID_SAVE_SNACKBAR));
@@ -190,13 +205,17 @@
* @param selector {@link BySelector} that identifies the object.
*/
private UiObject2 waitForObject(BySelector selector) {
- final boolean gotIt = mDevice.wait(Until.hasObject(selector), mTimeout);
- assertWithMessage("object for '%s' not found in %s ms", selector, mTimeout).that(gotIt)
- .isTrue();
-
- final UiObject2 uiObject = mDevice.findObject(selector);
- assertWithMessage("object for '%s' null in %s ms", selector, mTimeout).that(uiObject)
- .isNotNull();
- return uiObject;
+ // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
+ final int maxTries = 5;
+ final int napTime = mTimeout / maxTries;
+ for (int i = 1; i <= maxTries; i++) {
+ final UiObject2 uiObject = mDevice.findObject(selector);
+ if (uiObject != null) {
+ return uiObject;
+ }
+ SystemClock.sleep(napTime);
+ }
+ throw new AssertionError("Object with selector " + selector + " not found in "
+ + mTimeout + " ms");
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
index 480464d..b8a7f96 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
@@ -29,6 +29,8 @@
import android.view.View;
import android.view.autofill.AutoFillValue;
import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import org.junit.Before;
import org.junit.Rule;
@@ -65,6 +67,29 @@
mManualContainerInherit = (EditText) mActivity.findViewById(R.id.manualContainerInherit);
}
+ private void runOnUiThreadSync(@NonNull Runnable r) throws InterruptedException {
+ RuntimeException exceptionOnUiThread[] = {null};
+
+ synchronized (this) {
+ mActivity.runOnUiThread(() -> {
+ synchronized (this) {
+ try {
+ r.run();
+ } catch (RuntimeException e) {
+ exceptionOnUiThread[0] = e;
+ }
+ this.notify();
+ }
+ });
+
+ wait();
+ }
+
+ if (exceptionOnUiThread[0] != null) {
+ throw exceptionOnUiThread[0];
+ }
+ }
+
/**
* Sets the expectation for an auto-fill request, so it can be asserted through
* {@link #assertAutoFilled()} later.
@@ -93,11 +118,17 @@
* @throws Exception If something unexpected happened
*/
private void checkFieldBehavior(@NonNull EditText field, boolean expectUI) throws Exception {
+ if (expectUI) {
+ assertThat(field.getResolvedAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_AUTO);
+ } else {
+ assertThat(field.getResolvedAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_MANUAL);
+ }
+
// Make sure the requestFocus triggers a change
if (field == mFirstLevelManual) {
- mActivity.runOnUiThread(() -> mFirstLevelDefault.requestFocus());
+ runOnUiThreadSync(() -> mFirstLevelDefault.requestFocus());
} else {
- mActivity.runOnUiThread(() -> mFirstLevelManual.requestFocus());
+ runOnUiThreadSync(() -> mFirstLevelManual.requestFocus());
}
enableService();
@@ -123,7 +154,7 @@
expectAutoFill();
- mActivity.runOnUiThread(() -> field.requestFocus());
+ runOnUiThreadSync(() -> field.requestFocus());
Throwable exceptionDuringAutoFillTrigger = null;
try {
@@ -143,8 +174,6 @@
} else {
assertThat(exceptionDuringAutoFillTrigger).isNotNull();
}
-
- waitUntilDisconnected();
} finally {
disableService();
}
@@ -233,17 +262,146 @@
}
@Test
- public void checkSet() {
- View v = mActivity.findViewById(R.id.firstLevelDefault);
-
- v.setAutoFillMode(View.AUTO_FILL_MODE_MANUAL);
- assertThat(v.getAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_MANUAL);
+ public void checkSetAutoFillMode() {
+ mFirstLevelDefault.setAutoFillMode(View.AUTO_FILL_MODE_MANUAL);
+ assertThat(mFirstLevelDefault.getAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_MANUAL);
}
@Test
- public void checkIllegalSet() throws Exception {
- View v = mActivity.findViewById(R.id.firstLevelDefault);
+ public void checkIllegalAutoFillModeSet() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> mFirstLevelDefault.setAutoFillMode(-1));
+ }
- assertThrows(IllegalArgumentException.class, () -> v.setAutoFillMode(-1));
+ @Test
+ public void checkTextViewNoHint() {
+ assertThat(mActivity.findViewById(R.id.textViewNoHint).getAutoFillHint()).isEqualTo(
+ View.AUTO_FILL_HINT_NONE);
+ }
+
+ @Test
+ public void checkTextViewHintNone() {
+ assertThat(mActivity.findViewById(R.id.textViewHintNone).getAutoFillHint()).isEqualTo(
+ View.AUTO_FILL_HINT_NONE);
+ }
+
+ @Test
+ public void checkTextViewPassword() {
+ assertThat(mActivity.findViewById(R.id.textViewPassword).getAutoFillHint()).isEqualTo(
+ View.AUTO_FILL_HINT_PASSWORD);
+ }
+
+ @Test
+ public void checkTextViewPhoneName() {
+ assertThat(mActivity.findViewById(R.id.textViewPhoneName).getAutoFillHint()).isEqualTo(
+ View.AUTO_FILL_HINT_PHONE | View.AUTO_FILL_HINT_USERNAME);
+ }
+
+ @Test
+ public void checkSetAutoFill() {
+ View v = mActivity.findViewById(R.id.textViewNoHint);
+
+ v.setAutoFillHint(View.AUTO_FILL_HINT_NONE);
+ assertThat(v.getAutoFillHint()).isEqualTo(View.AUTO_FILL_HINT_NONE);
+
+ v.setAutoFillHint(View.AUTO_FILL_HINT_PASSWORD);
+ assertThat(v.getAutoFillHint()).isEqualTo(View.AUTO_FILL_HINT_PASSWORD);
+
+ v.setAutoFillHint(View.AUTO_FILL_HINT_PASSWORD | View.AUTO_FILL_HINT_EMAIL_ADDRESS);
+ assertThat(v.getAutoFillHint()).isEqualTo(View.AUTO_FILL_HINT_PASSWORD
+ | View.AUTO_FILL_HINT_EMAIL_ADDRESS);
+ }
+
+ @Test
+ public void attachViewToManualContainer() throws Exception {
+ runOnUiThreadSync(() -> mFirstLevelManual.requestFocus());
+ enableService();
+
+ try {
+ View view = new TextView(mActivity);
+
+ view.setLayoutParams(
+ new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ assertThat(view.getAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_INHERIT);
+ assertThat(view.getResolvedAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_AUTO);
+
+ // Requesting focus should not trigger any mishaps
+ runOnUiThreadSync(() -> view.requestFocus());
+
+ LinearLayout attachmentPoint = (LinearLayout) mActivity.findViewById(
+ R.id.manualContainer);
+ runOnUiThreadSync(() -> attachmentPoint.addView(view));
+
+ assertThat(view.getResolvedAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_MANUAL);
+ } finally {
+ disableService();
+ }
+ }
+
+ @Test
+ public void attachNestedViewToContainer() throws Exception {
+ runOnUiThreadSync(() -> mFirstLevelManual.requestFocus());
+ enableService();
+
+ try {
+ // Create view and viewGroup but do not attach to window
+ LinearLayout container = (LinearLayout) mActivity.getLayoutInflater().inflate(
+ R.layout.nested_layout, null);
+ EditText field = container.findViewById(R.id.field);
+
+ assertThat(field.getAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_INHERIT);
+ assertThat(container.getAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_INHERIT);
+
+ // Resolved mode for detached views should behave as documented
+ assertThat(field.getResolvedAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_AUTO);
+ assertThat(container.getResolvedAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_AUTO);
+
+ // Requesting focus should not trigger any mishaps
+ runOnUiThreadSync(() -> field.requestFocus());
+
+ // Set up auto-fill service and response
+ final InstrumentedAutoFillService.Replier replier =
+ new InstrumentedAutoFillService.Replier();
+ InstrumentedAutoFillService.setReplier(replier);
+
+ replier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField("field", AutoFillValue.forText("filled"))
+ .setPresentation(createPresentation("dataset"))
+ .build())
+ .build());
+
+ OneTimeTextWatcher mViewWatcher = new OneTimeTextWatcher("field", field, "filled");
+ field.addTextChangedListener(mViewWatcher);
+
+ // As the focus is set to "field", attaching "container" should trigger an auto-fill
+ // request on "field"
+ LinearLayout attachmentPoint = (LinearLayout) mActivity.findViewById(
+ R.id.rootContainer);
+ runOnUiThreadSync(() -> attachmentPoint.addView(container));
+
+ // Now the resolved auto-fill modes make sense, hence check them
+ assertThat(field.getResolvedAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_AUTO);
+ assertThat(container.getResolvedAutoFillMode()).isEqualTo(View.AUTO_FILL_MODE_AUTO);
+
+ // We should now be able to select the data set
+ waitUntilConnected();
+ sUiBot.selectDataset("dataset");
+
+ // Check if auto-fill operation worked
+ mViewWatcher.assertAutoFilled();
+ } finally {
+ disableService();
+ }
+ }
+
+ @Test
+ public void checkSetAutoFillUnknown() {
+ View v = mActivity.findViewById(R.id.textViewNoHint);
+
+ // Unknown values are allowed
+ v.setAutoFillHint(-1);
+ assertThat(v.getAutoFillHint()).isEqualTo(-1);
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.java b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.java
index 1e1ee72..d9a8cf3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.java
@@ -16,15 +16,14 @@
package android.autofillservice.cts;
-import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
-public class ViewAttributesTestActivity extends Activity {
+public class ViewAttributesTestActivity extends AbstractAutoFillActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.partially_manually_filled_activity);
+ setContentView(R.layout.view_attribute_test_activity);
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
index 3eca661..637966d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
@@ -15,7 +15,6 @@
*/
package android.autofillservice.cts;
-import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
@@ -25,7 +24,9 @@
/**
* Activity that displays a "Welcome USER" message after login.
*/
-public class WelcomeActivity extends Activity {
+public class WelcomeActivity extends AbstractAutoFillActivity {
+
+ private static WelcomeActivity sInstance;
private static final String TAG = "WelcomeActivity";
@@ -33,6 +34,10 @@
private TextView mOutput;
+ public WelcomeActivity() {
+ sInstance = this;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -50,4 +55,11 @@
Log.d(TAG, "Output: " + mOutput.getText());
}
+
+ static void finishIt() {
+ if (sInstance != null) {
+ Log.d(TAG, "So long and thanks for all the fish!");
+ sInstance.finish();
+ }
+ }
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index e8995ae..a900b84 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -444,13 +444,10 @@
continue;
}
- int[] availableAfModes = mStaticInfo.getCharacteristics().get(
- CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
- int[] availableAeModes = mStaticInfo.getCharacteristics().get(
- CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
+ int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
+ int[] availableAeModes = mStaticInfo.getAeAvailableModesChecked();
for (int afMode : availableAfModes) {
-
if (afMode == CameraCharacteristics.CONTROL_AF_MODE_OFF ||
afMode == CameraCharacteristics.CONTROL_AF_MODE_EDOF) {
// Only test AF modes that have meaningful trigger behavior
@@ -585,13 +582,10 @@
continue;
}
- int[] availableAfModes = mStaticInfo.getCharacteristics().get(
- CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
- int[] availableAeModes = mStaticInfo.getCharacteristics().get(
- CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
+ int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
+ int[] availableAeModes = mStaticInfo.getAeAvailableModesChecked();
for (int afMode : availableAfModes) {
-
if (afMode == CameraCharacteristics.CONTROL_AF_MODE_OFF ||
afMode == CameraCharacteristics.CONTROL_AF_MODE_EDOF) {
// Only test AF modes that have meaningful trigger behavior
@@ -689,13 +683,10 @@
continue;
}
- int[] availableAfModes = mStaticInfo.getCharacteristics().get(
- CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
- int[] availableAeModes = mStaticInfo.getCharacteristics().get(
- CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
+ int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
+ int[] availableAeModes = mStaticInfo.getAeAvailableModesChecked();
for (int afMode : availableAfModes) {
-
if (afMode == CameraCharacteristics.CONTROL_AF_MODE_OFF ||
afMode == CameraCharacteristics.CONTROL_AF_MODE_EDOF) {
// Only test AF modes that have meaningful trigger behavior
@@ -807,13 +798,10 @@
continue;
}
- int[] availableAfModes = mStaticInfo.getCharacteristics().get(
- CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
- int[] availableAeModes = mStaticInfo.getCharacteristics().get(
- CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
+ int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
+ int[] availableAeModes = mStaticInfo.getAeAvailableModesChecked();
for (int afMode : availableAfModes) {
-
if (afMode == CameraCharacteristics.CONTROL_AF_MODE_OFF ||
afMode == CameraCharacteristics.CONTROL_AF_MODE_EDOF) {
// Only test AF modes that have meaningful trigger behavior
@@ -991,8 +979,8 @@
private CaptureRequest.Builder prepareTriggerTestSession(
SurfaceTexture preview, int aeMode, int afMode) throws Exception {
Log.i(TAG, String.format("Testing AE mode %s, AF mode %s",
- StaticMetadata.AE_MODE_NAMES[aeMode],
- StaticMetadata.AF_MODE_NAMES[afMode]));
+ StaticMetadata.getAeModeName(aeMode),
+ StaticMetadata.getAfModeName(afMode)));
CaptureRequest.Builder previewRequest = preparePreviewTestSession(preview);
previewRequest.set(CaptureRequest.CONTROL_AE_MODE, aeMode);
@@ -1046,7 +1034,7 @@
// After several frames, AF must no longer be in INACTIVE state
assertTrue(String.format("In AF mode %s, AF state not PASSIVE_SCAN" +
", PASSIVE_FOCUSED, or PASSIVE_UNFOCUSED, is %s",
- StaticMetadata.AF_MODE_NAMES[afMode],
+ StaticMetadata.getAfModeName(afMode),
StaticMetadata.AF_STATE_NAMES[afState]),
afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN ||
afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED ||
@@ -1067,7 +1055,7 @@
private boolean verifyAfSequence(int afMode, int afState, boolean focusComplete) {
if (focusComplete) {
assertTrue(String.format("AF Mode %s: Focus lock lost after convergence: AF state: %s",
- StaticMetadata.AF_MODE_NAMES[afMode],
+ StaticMetadata.getAfModeName(afMode),
StaticMetadata.AF_STATE_NAMES[afState]),
afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
afState ==CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED);
@@ -1075,14 +1063,14 @@
}
if (VERBOSE) {
Log.v(TAG, String.format("AF mode: %s, AF state: %s",
- StaticMetadata.AF_MODE_NAMES[afMode],
+ StaticMetadata.getAfModeName(afMode),
StaticMetadata.AF_STATE_NAMES[afState]));
}
switch (afMode) {
case CaptureResult.CONTROL_AF_MODE_AUTO:
case CaptureResult.CONTROL_AF_MODE_MACRO:
assertTrue(String.format("AF mode %s: Unexpected AF state %s",
- StaticMetadata.AF_MODE_NAMES[afMode],
+ StaticMetadata.getAfModeName(afMode),
StaticMetadata.AF_STATE_NAMES[afState]),
afState == CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN ||
afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
@@ -1092,7 +1080,7 @@
break;
case CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE:
assertTrue(String.format("AF mode %s: Unexpected AF state %s",
- StaticMetadata.AF_MODE_NAMES[afMode],
+ StaticMetadata.getAfModeName(afMode),
StaticMetadata.AF_STATE_NAMES[afState]),
afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN ||
afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
@@ -1102,14 +1090,14 @@
break;
case CaptureResult.CONTROL_AF_MODE_CONTINUOUS_VIDEO:
assertTrue(String.format("AF mode %s: Unexpected AF state %s",
- StaticMetadata.AF_MODE_NAMES[afMode],
+ StaticMetadata.getAfModeName(afMode),
StaticMetadata.AF_STATE_NAMES[afState]),
afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED);
focusComplete = true;
break;
default:
- fail("Unexpected AF mode: " + StaticMetadata.AF_MODE_NAMES[afMode]);
+ fail("Unexpected AF mode: " + StaticMetadata.getAfModeName(afMode));
}
return focusComplete;
}
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index dea3cfd..319ec91 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -73,7 +73,7 @@
private final CheckLevel mLevel;
private final CameraErrorCollector mCollector;
- // Index with android.control.aeMode
+ // Access via getAeModeName() to account for vendor extensions
public static final String[] AE_MODE_NAMES = new String[] {
"AE_MODE_OFF",
"AE_MODE_ON",
@@ -82,7 +82,7 @@
"AE_MODE_ON_AUTO_FLASH_REDEYE"
};
- // Index with android.control.afMode
+ // Access via getAfModeName() to account for vendor extensions
public static final String[] AF_MODE_NAMES = new String[] {
"AF_MODE_OFF",
"AF_MODE_AUTO",
@@ -434,6 +434,16 @@
return calibration;
}
+ public static String getAeModeName(int aeMode) {
+ return (aeMode >= AE_MODE_NAMES.length) ? String.format("VENDOR_AE_MODE_%d", aeMode) :
+ AE_MODE_NAMES[aeMode];
+ }
+
+ public static String getAfModeName(int afMode) {
+ return (afMode >= AF_MODE_NAMES.length) ? String.format("VENDOR_AF_MODE_%d", afMode) :
+ AF_MODE_NAMES[afMode];
+ }
+
/**
* Get max AE regions and do sanity check.
*
@@ -1139,9 +1149,16 @@
}
List<Integer> modeList = new ArrayList<Integer>();
for (int mode : modes) {
- modeList.add(mode);
+ // Skip vendor-added modes
+ if (mode <= CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE) {
+ modeList.add(mode);
+ }
}
checkTrueForKey(modesKey, "value is empty", !modeList.isEmpty());
+ modes = new int[modeList.size()];
+ for (int i = 0; i < modeList.size(); i++) {
+ modes[i] = modeList.get(i);
+ }
// All camera device must support ON
checkTrueForKey(modesKey, "values " + modeList.toString() + " must contain ON mode",
@@ -1227,7 +1244,17 @@
return new int[0];
}
- List<Integer> modesList = Arrays.asList(CameraTestUtils.toObject(afModes));
+ List<Integer> modesList = new ArrayList<Integer>();
+ for (int afMode : afModes) {
+ // Skip vendor-added AF modes
+ if (afMode > CameraCharacteristics.CONTROL_AF_MODE_EDOF) continue;
+ modesList.add(afMode);
+ }
+ afModes = new int[modesList.size()];
+ for (int i = 0; i < modesList.size(); i++) {
+ afModes[i] = modesList.get(i);
+ }
+
if (isHardwareLevelAtLeastLimited()) {
// Some LEGACY mode devices do not support AF OFF
checkTrueForKey(key, " All camera devices must support OFF mode",
diff --git a/tests/fragment/AndroidManifest.xml b/tests/fragment/AndroidManifest.xml
index 05027b5..ecbd39b 100644
--- a/tests/fragment/AndroidManifest.xml
+++ b/tests/fragment/AndroidManifest.xml
@@ -27,6 +27,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity android:name=".LoaderActivity"/>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java b/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
index b1d7411..5547946 100644
--- a/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
+++ b/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
@@ -32,6 +32,9 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
public class FragmentTestUtil {
public static void waitForExecution(final ActivityTestRule<FragmentTestActivity> rule) {
// Wait for two cycles. When starting a postponed transition, it will post to
@@ -43,7 +46,7 @@
instrumentation.runOnMainSync(() -> {});
}
- private static void runOnUiThreadRethrow(ActivityTestRule<FragmentTestActivity> rule,
+ private static void runOnUiThreadRethrow(ActivityTestRule<? extends Activity> rule,
Runnable r) {
if (Looper.getMainLooper() == Looper.myLooper()) {
r.run();
@@ -57,12 +60,12 @@
}
public static boolean executePendingTransactions(
- final ActivityTestRule<FragmentTestActivity> rule) {
+ final ActivityTestRule<? extends Activity> rule) {
return executePendingTransactions(rule, rule.getActivity().getFragmentManager());
}
public static boolean executePendingTransactions(
- final ActivityTestRule<FragmentTestActivity> rule, final FragmentManager fm) {
+ final ActivityTestRule<? extends Activity> rule, final FragmentManager fm) {
final boolean[] ret = new boolean[1];
runOnUiThreadRethrow(rule, new Runnable() {
@Override
@@ -197,4 +200,21 @@
accessibilityNodeInfo.recycle();
return isVisible;
}
+
+ /**
+ * Allocates until a garbage collection occurs.
+ */
+ public static void forceGC() {
+ // Do it twice so that we know we're not in the middle of the first collection when
+ // returning.
+ for (int i = 0; i < 2; i++) {
+ // Use a random index in the list to detect the garbage collection each time because
+ // .get() may accidentally trigger a strong reference during collection.
+ ArrayList<WeakReference<byte[]>> leak = new ArrayList<>();
+ do {
+ WeakReference<byte[]> arr = new WeakReference<byte[]>(new byte[100]);
+ leak.add(arr);
+ } while (leak.get((int) (Math.random() * leak.size())).get() != null);
+ }
+ }
}
diff --git a/tests/fragment/src/android/fragment/cts/LoaderActivity.java b/tests/fragment/src/android/fragment/cts/LoaderActivity.java
new file mode 100644
index 0000000..bd5a1cc
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/LoaderActivity.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.fragment.cts;
+
+import android.app.Activity;
+import android.app.LoaderManager;
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.content.Loader;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * This Activity sets the text when loading completes. It also tracks the Activity in
+ * a static variable, so it must be cleared in test tear down.
+ */
+public class LoaderActivity extends Activity {
+ // These must be cleared after each test using clearState()
+ public static LoaderActivity sActivity;
+ public static CountDownLatch sResumed;
+
+ public TextView textView;
+
+ public static void clearState() {
+ sActivity = null;
+ sResumed = null;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sActivity = this;
+
+ setContentView(R.layout.text_a);
+ textView = (TextView) findViewById(R.id.textA);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ getLoaderManager().initLoader(0, null, new TextLoaderCallback());
+ if (sResumed != null) {
+ sResumed.countDown();
+ }
+ }
+
+ class TextLoaderCallback implements LoaderManager.LoaderCallbacks<String> {
+ @Override
+ public Loader<String> onCreateLoader(int id, Bundle args) {
+ return new TextLoader(LoaderActivity.this);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<String> loader, String data) {
+ textView.setText(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<String> loader) {
+ }
+ }
+
+ static class TextLoader extends AsyncTaskLoader<String> {
+ TextLoader(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ forceLoad();
+ }
+
+ @Override
+ public String loadInBackground() {
+ return "Loaded!";
+ }
+ }
+}
diff --git a/tests/fragment/src/android/fragment/cts/LoaderTest.java b/tests/fragment/src/android/fragment/cts/LoaderTest.java
new file mode 100755
index 0000000..9e3097c
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/LoaderTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.fragment.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.Instrumentation;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class LoaderTest {
+ @Rule
+ public ActivityTestRule<LoaderActivity> mActivityRule =
+ new ActivityTestRule<>(LoaderActivity.class);
+
+ @After
+ public void clearActivity() {
+ LoaderActivity.clearState();
+ }
+
+ /**
+ * Test to ensure that there is no Activity leak due to Loader
+ */
+ @Test
+ public void testLeak() throws Throwable {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent(mActivityRule.getActivity(), LoaderActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ LoaderActivity.sResumed = new CountDownLatch(1);
+ instrumentation.startActivitySync(intent);
+ assertTrue(LoaderActivity.sResumed.await(1, TimeUnit.SECONDS));
+
+ LoaderFragment fragment = new LoaderFragment();
+ FragmentManager fm = LoaderActivity.sActivity.getFragmentManager();
+
+ fm.beginTransaction()
+ .add(fragment, "1")
+ .commit();
+
+ FragmentTestUtil.executePendingTransactions(mActivityRule, fm);
+
+ fm.beginTransaction()
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit();
+
+ FragmentTestUtil.executePendingTransactions(mActivityRule, fm);
+
+ WeakReference<LoaderActivity> weakActivity = new WeakReference(LoaderActivity.sActivity);
+
+ if (!switchOrientation()) {
+ return; // can't switch orientation for square screens
+ }
+
+ // Now force a garbage collection.
+ FragmentTestUtil.forceGC();
+ assertNull(weakActivity.get());
+ }
+
+ /**
+ * When a LoaderManager is reused, it should notify in onResume
+ */
+ @Test
+ public void startWhenReused() throws Throwable {
+ LoaderActivity activity = mActivityRule.getActivity();
+
+ assertEquals("Loaded!", activity.textView.getText().toString());
+
+ if (!switchOrientation()) {
+ return; // can't switch orientation for square screens
+ }
+
+ // After orientation change, the text should still be loaded properly
+ activity = LoaderActivity.sActivity;
+ assertEquals("Loaded!", activity.textView.getText().toString());
+ }
+
+ private boolean switchOrientation() throws InterruptedException {
+ LoaderActivity activity = LoaderActivity.sActivity;
+
+ int currentOrientation = activity.getResources().getConfiguration().orientation;
+
+ int nextOrientation;
+ if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+ nextOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ } else if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
+ nextOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ } else {
+ return false; // Don't know what to do with square or unknown orientations
+ }
+
+ // Now switch the orientation
+ LoaderActivity.sResumed = new CountDownLatch(1);
+
+ activity.setRequestedOrientation(nextOrientation);
+ assertTrue(LoaderActivity.sResumed.await(1, TimeUnit.SECONDS));
+ return true;
+ }
+
+
+ public static class LoaderFragment extends Fragment {
+ private static final int LOADER_ID = 1;
+ private final LoaderManager.LoaderCallbacks<Boolean> mLoaderCallbacks =
+ new LoaderManager.LoaderCallbacks<Boolean>() {
+ @Override
+ public Loader<Boolean> onCreateLoader(int id, Bundle args) {
+ return new DummyLoader(getContext());
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Boolean> loader, Boolean data) {
+
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Boolean> loader) {
+
+ }
+ };
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ getLoaderManager().initLoader(LOADER_ID, null, mLoaderCallbacks);
+ }
+ }
+
+ static class DummyLoader extends Loader<Boolean> {
+ DummyLoader(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ deliverResult(true);
+ }
+ }
+}
diff --git a/tests/signature/src/android/signature/cts/FailureType.java b/tests/signature/src/android/signature/cts/FailureType.java
index 5aaebc4..a701202 100644
--- a/tests/signature/src/android/signature/cts/FailureType.java
+++ b/tests/signature/src/android/signature/cts/FailureType.java
@@ -10,6 +10,7 @@
MISSING_FIELD,
MISMATCH_CLASS,
MISMATCH_INTERFACE,
+ MISMATCH_INTERFACE_METHOD,
MISMATCH_METHOD,
MISMATCH_FIELD,
CAUGHT_EXCEPTION,
diff --git a/tests/signature/src/android/signature/cts/JDiffClassDescription.java b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
index 35e97d0..2779ea7 100644
--- a/tests/signature/src/android/signature/cts/JDiffClassDescription.java
+++ b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
@@ -51,6 +51,23 @@
/** Indicates that the method is a synthetic method. */
private static final int METHOD_MODIFIER_SYNTHETIC = 0x00001000;
+ private static final Set<String> HIDDEN_INTERFACE_WHITELIST = new HashSet<>();
+ static {
+ // Interfaces that define @hide methods will by definition contain
+ // methods that do not appear in current.txt. Interfaces added to this
+ // list are probably not meant to be implemented in an application.
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract boolean android.companion.DeviceFilter.matches(D)");
+ HIDDEN_INTERFACE_WHITELIST.add("public static <D> boolean android.companion.DeviceFilter.matches(android.companion.DeviceFilter<D>,D)");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.nfc.tech.TagTechnology.reconnect() throws java.io.IOException");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.os.IBinder.shellCommand(java.io.FileDescriptor,java.io.FileDescriptor,java.io.FileDescriptor,java.lang.String[],android.os.ShellCallback,android.os.ResultReceiver) throws android.os.RemoteException");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract int android.text.ParcelableSpan.getSpanTypeIdInternal()");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.text.ParcelableSpan.writeToParcelInternal(android.os.Parcel,int)");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.view.WindowManager.requestAppKeyboardShortcuts(android.view.WindowManager$KeyboardShortcutsReceiver,int)");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract boolean javax.microedition.khronos.egl.EGL10.eglReleaseThread()");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void org.w3c.dom.ls.LSSerializer.setFilter(org.w3c.dom.ls.LSSerializerFilter)");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract org.w3c.dom.ls.LSSerializerFilter org.w3c.dom.ls.LSSerializer.getFilter()");
+ }
+
public enum JDiffType {
INTERFACE, CLASS
}
@@ -1100,6 +1117,40 @@
}
/**
+ * Validate that an interfaces method count is as expected.
+ */
+ private List<String> checkInterfaceMethodCompliance() {
+ List<String> unexpectedMethods = new ArrayList<>();
+ for (Method method : mClass.getDeclaredMethods()) {
+ if (method.isDefault()) {
+ continue;
+ }
+ if (method.isSynthetic()) {
+ continue;
+ }
+ if (method.isBridge()) {
+ continue;
+ }
+ if (HIDDEN_INTERFACE_WHITELIST.contains(method.toGenericString())) {
+ continue;
+ }
+
+ boolean foundMatch = false;
+ for (JDiffMethod jdiffMethod : jDiffMethods) {
+ if (matches(jdiffMethod, method)) {
+ foundMatch = true;
+ }
+ }
+ if (!foundMatch) {
+ unexpectedMethods.add(method.toGenericString());
+ }
+ }
+
+ return unexpectedMethods;
+
+ }
+
+ /**
* Checks that the class found through reflection matches the
* specification from the API xml file.
*/
@@ -1123,6 +1174,15 @@
return;
}
+
+ List<String> methods = checkInterfaceMethodCompliance();
+ if (JDiffType.INTERFACE.equals(mClassType) && methods.size() > 0) {
+ mResultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE_METHOD,
+ mAbsoluteClassName, "Interfaces cannot be modified: "
+ + mAbsoluteClassName + ": " + methods);
+ return;
+ }
+
if (!checkClassModifiersCompliance()) {
logMismatchInterfaceSignature(mAbsoluteClassName,
"Non-compatible class found when looking for " +
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
index 2b29071..019fb66 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
@@ -24,6 +24,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherApps;
+import android.os.Bundle;
import android.os.Handler;
import java.util.Arrays;
@@ -62,10 +63,13 @@
BlockingReceiver setupReceiver = new BlockingReceiver()
.register(Constants.ACTION_SETUP_REPLY);
+ Bundle extras = new Bundle();
+ extras.putString("dummy", launcherPkg + "-dummy");
+
PendingIntent pinResult = PendingIntent.getBroadcast(context, 0,
new Intent(ACTION_PIN_RESULT), PendingIntent.FLAG_ONE_SHOT);
AppWidgetManager.getInstance(context).requestPinAppWidget(
- getFirstWidgetComponent(), pinResult);
+ getFirstWidgetComponent(), extras, pinResult);
setupReceiver.await();
// Verify that the confirmation dialog was opened
@@ -80,6 +84,8 @@
boolean[] providerInfo = verifyInstalledProviders(Arrays.asList(
req.getAppWidgetProviderInfo(context), req.getAppWidgetProviderInfo(context)));
assertTrue(providerInfo[0]);
+ assertNotNull(req.getExtras());
+ assertEquals(launcherPkg + "-dummy", req.getExtras().getString("dummy"));
// Accept the request
BlockingReceiver resultReceiver = new BlockingReceiver().register(ACTION_PIN_RESULT);
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index 47813f3..b69a8c5 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -28,6 +28,7 @@
import android.provider.AlarmClock;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
import android.test.AndroidTestCase;
@@ -50,6 +51,23 @@
}
/**
+ * Assert target intent is not resolved by a filter with priority greater than 0.
+ * @param intent - the Intent will be handled.
+ */
+ private void assertDefaultHandlerValidPriority(final Intent intent) {
+ PackageManager packageManager = mContext.getPackageManager();
+ List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0);
+ assertNotNull(resolveInfoList);
+ // one or more activity can handle this intent.
+ assertTrue(resolveInfoList.size() > 0);
+ // no activities override defaults with a high priority. Only system activities can override
+ // the priority.
+ for (ResolveInfo resolveInfo : resolveInfoList) {
+ assertTrue(resolveInfo.priority <= 0);
+ }
+ }
+
+ /**
* Test ACTION_VIEW when url is http://web_address,
* it will open a browser window to the URL specified.
*/
@@ -308,4 +326,16 @@
public void testManageStorage() {
assertCanBeHandled(new Intent(StorageManager.ACTION_MANAGE_STORAGE));
}
+
+ public void testVoiceCommand() {
+ Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
+ assertCanBeHandled(intent);
+ assertDefaultHandlerValidPriority(intent);
+ }
+
+ public void testVoiceSearchHandsFree() {
+ Intent intent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ assertCanBeHandled(intent);
+ assertDefaultHandlerValidPriority(intent);
+ }
}
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index 5992add..0e4c3d6 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -342,7 +342,7 @@
WindowManager.LayoutParams params = getWindow().getAttributes();
params.preferredDisplayModeId = mModeId;
- params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+ params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
params.setTitle("CtsTestPresentation");
getWindow().setAttributes(params);
}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java b/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java
index 6cf7e29..1b00975 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java
@@ -15,6 +15,10 @@
*/
package android.graphics.fonts.cts;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -22,23 +26,31 @@
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.Base64;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Tests for {@link android.graphics.fonts.FontRequest}.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class FontRequestTest {
- private static final String PROVIDER = "com.test.fontprovider";
- private static final String QUERY = "my_family_name";
+ private static final String PROVIDER = "com.test.fontprovider.authority";
+ private static final String QUERY = "query";
+ private static final String PACKAGE = "com.test.fontprovider.package";
+ private static final byte[] BYTE_ARRAY =
+ Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
+ private static final List<List<byte[]>> CERTS = Arrays.asList(Arrays.asList(BYTE_ARRAY));
@Test
public void testWriteToParcel() {
- // GIVEN a FontRequest
- FontRequest request = new FontRequest(PROVIDER, QUERY);
+ // GIVEN a FontRequest created with the long constructor
+ FontRequest request = new FontRequest(PROVIDER, PACKAGE, QUERY, CERTS);
// WHEN we write it to a Parcel
Parcel dest = Parcel.obtain();
@@ -48,30 +60,76 @@
// THEN we create from that parcel and get the same values.
FontRequest result = FontRequest.CREATOR.createFromParcel(dest);
assertEquals(PROVIDER, result.getProviderAuthority());
+ assertEquals(PACKAGE, result.getProviderPackage());
assertEquals(QUERY, result.getQuery());
+ assertEquals(CERTS.size(), result.getCertificates().size());
+ List<byte[]> cert = CERTS.get(0);
+ List<byte[]> resultCert = result.getCertificates().get(0);
+ assertEquals(cert.size(), resultCert.size());
+ assertTrue(Arrays.equals(cert.get(0), resultCert.get(0)));
}
@Test
- public void testConstructorWithNullAuthority() {
- try {
- // WHEN we create a request with a null authority
- new FontRequest(null, QUERY);
- } catch (NullPointerException e) {
- // THEN we expect an exception to be raised.
- return;
- }
- fail();
+ public void testWriteToParcel_shortConstructor() {
+ // GIVEN a FontRequest created with the short constructor
+ FontRequest request = new FontRequest(PROVIDER, PACKAGE, QUERY);
+
+ // WHEN we write it to a Parcel
+ Parcel dest = Parcel.obtain();
+ request.writeToParcel(dest, 0);
+ dest.setDataPosition(0);
+
+ // THEN we create from that parcel and get the same values.
+ FontRequest result = FontRequest.CREATOR.createFromParcel(dest);
+ assertEquals(PROVIDER, result.getProviderAuthority());
+ assertEquals(PACKAGE, result.getProviderPackage());
+ assertEquals(QUERY, result.getQuery());
+ assertNotNull(result.getCertificates());
+ assertEquals(0, result.getCertificates().size());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testShortConstructor_nullAuthority() {
+ new FontRequest(null, PACKAGE, QUERY);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testShortConstructor_nullPackage() {
+ new FontRequest(PROVIDER, null, QUERY);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testShortConstructor_nullQuery() {
+ new FontRequest(PROVIDER, PACKAGE, null);
}
@Test
- public void testConstructorWithNullQuery() {
- try {
- // WHEN we create a request with a null query
- new FontRequest(PROVIDER, null);
- } catch (NullPointerException e) {
- // THEN we expect an exception to be raised.
- return;
- }
- fail();
+ public void testShortConstructor_defaults() {
+ // WHEN we create a request with the short constructor
+ FontRequest request = new FontRequest(PROVIDER, PACKAGE, QUERY);
+
+ // THEN we expect the defaults to be the following values
+ assertNotNull(request.getCertificates());
+ assertEquals(0, request.getCertificates().size());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLongConstructor_nullAuthority() {
+ new FontRequest(null, PACKAGE, QUERY, CERTS);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLongConstructor_nullPackage() {
+ new FontRequest(PROVIDER, null, QUERY, CERTS);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLongConstructor_nullQuery() {
+ new FontRequest(PROVIDER, PACKAGE, null, CERTS);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLongConstructor_nullCerts() {
+ new FontRequest(PROVIDER, PACKAGE, QUERY, null);
}
}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java b/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java
index 06e2e3f..4f961de 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java
@@ -90,16 +90,9 @@
assertEquals(STYLE, result.getStyle());
}
- @Test
+ @Test(expected = NullPointerException.class)
public void testConstructorWithNullFileDescriptor() {
- try {
- // WHEN we create a result with a null file descriptor
- new FontResult(null, TTC_INDEX, FONT_VARIATION_SETTINGS, STYLE);
- } catch (NullPointerException e) {
- // THEN we expect an exception to be raised.
- return;
- }
- fail();
+ new FontResult(null, TTC_INDEX, FONT_VARIATION_SETTINGS, STYLE);
}
@Test
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
index 9dbcd5e..813742a 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
@@ -1350,7 +1350,20 @@
for (Size sz : standardSizes) {
MediaFormat format = MediaFormat.createVideoFormat(
MIME_TYPE, sz.getWidth(), sz.getHeight());
- format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); // require at least 15fps
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ int bitRate = BITRATE_DEFAULT;
+ if (sz.getWidth() == 1920 && sz.getHeight() == 1080) {
+ bitRate = BITRATE_1080p;
+ } else if (sz.getWidth() == 1280 && sz.getHeight() == 720) {
+ bitRate = BITRATE_720p;
+ } else if (sz.getWidth() == 800 && sz.getHeight() == 480) {
+ bitRate = BITRATE_800x480;
+ }
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ Log.i(TAG,"format = " + format.toString());
if (mcl.findEncoderForFormat(format) != null) {
return sz;
}
diff --git a/tests/tests/nativemidi/Android.mk b/tests/tests/nativemidi/Android.mk
new file mode 100755
index 0000000..f82b54d
--- /dev/null
+++ b/tests/tests/nativemidi/Android.mk
@@ -0,0 +1,50 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+#
+# libnativemidi
+#
+#include $(CLEAR_VARS)
+#LOCAL_MODULE := libnativemidi
+#LOCAL_SRC_FILES := out/target/product/marlin/obj_arm/STATIC_LIBRARIES/libnativemidi_intermediates/libnativemidi.a
+#include $(PREBUILT_STATIC_LIBRARY)
+
+#
+# NativeMidiEchoTest
+#
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+#LOCAL_WHOLE_STATIC_LIBRARIES := libnativemidi
+LOCAL_JNI_SHARED_LIBRARIES := libnativemidi_jni
+#LOCAL_SHARED_LIBRARIES := libnativemidi_jni
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_PACKAGE_NAME := CtsNativeMidiTestCases
+
+#LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/nativemidi/AndroidManifest.xml b/tests/tests/nativemidi/AndroidManifest.xml
new file mode 100755
index 0000000..1d166a9
--- /dev/null
+++ b/tests/tests/nativemidi/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.nativemidi.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+ <uses-feature android:name="android.software.midi" android:required="true"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <service android:name="NativeMidiEchoTestService"
+ android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+ <intent-filter>
+ <action android:name="android.media.midi.MidiDeviceService" />
+ </intent-filter>
+ <meta-data android:name="android.media.midi.MidiDeviceService"
+ android:resource="@xml/echo_device_info" />
+ </service>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="CTS Native MIDI tests"
+ android:targetPackage="android.nativemidi.cts" >
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/nativemidi/AndroidTest.xml b/tests/tests/nativemidi/AndroidTest.xml
new file mode 100644
index 0000000..cf0ff45
--- /dev/null
+++ b/tests/tests/nativemidi/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS MIDI test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsMidiTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.midi.cts" />
+ <option name="runtime-hint" value="8m" />
+ </test>
+</configuration>
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
new file mode 100644
index 0000000..312a88b
--- /dev/null
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nativemidi.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDevice.MidiConnection;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceInfo.PortInfo;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiInputPort;
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiSender;
+import android.os.Bundle;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import android.util.Log;
+import java.util.Random;
+
+/**
+ * Test MIDI using a virtual MIDI device that echos input to output.
+ */
+public class NativeMidiEchoTest extends AndroidTestCase {
+ // Load the JNI shared library.
+ static {
+ System.loadLibrary("nativemidi_jni");
+ }
+
+ private static final String TAG = "NativeMidiEchoTest";
+
+ public static final String TEST_MANUFACTURER = "AndroidCTS";
+ public static final String ECHO_PRODUCT = "NativeMidiEcho";
+
+ private static final long NANOS_PER_MSEC = 1000L * 1000L;
+
+ // On a fast device in 2016, the test fails if timeout is 3 but works if it is 4.
+ // So this timeout value is very generous.
+ private static final int TIMEOUT_OPEN_MSEC = 2000; // arbitrary
+
+ private MidiManager mMidiManager;
+
+ private MidiDevice mEchoDevice;
+
+ // (Native code) attributes associated with a test/EchoServer instance.
+ private long mTestContext;
+
+ private Random mRandom = new Random(1972941337);
+
+ // Listens for an asynchronous device open and notifies waiting foreground
+ // test.
+ class MyTestOpenCallback implements MidiManager.OnDeviceOpenedListener {
+ MidiDevice mDevice;
+
+ @Override
+ public synchronized void onDeviceOpened(MidiDevice device) {
+ mDevice = device;
+ notifyAll();
+ }
+
+ public synchronized MidiDevice waitForOpen(int msec)
+ throws InterruptedException {
+ long deadline = System.currentTimeMillis() + msec;
+ long timeRemaining = msec;
+ while (mDevice == null && timeRemaining > 0) {
+ wait(timeRemaining);
+ timeRemaining = deadline - System.currentTimeMillis();
+ }
+ return mDevice;
+ }
+ }
+
+ public NativeMidiEchoTest() {
+ super();
+ Log.i(TAG, "NativeMidiEchoTest() [JAVA ctor]");
+ mTestContext = 0;
+ }
+
+ //
+ // Helpers
+ //
+ private boolean hasMidiSupport() {
+ PackageManager pm = mContext.getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_MIDI);
+ }
+
+ private void compareMessages(byte[] buffer, long timestamp, NativeMidiMessage nativeMsg) {
+ assertEquals("byte count of message", buffer.length, nativeMsg.len);
+ assertEquals("timestamp in message", timestamp, nativeMsg.timestamp);
+
+ for (int index = 0; index < buffer.length; index++) {
+ assertEquals("message byte[" + index + "]", buffer[index] & 0x0FF,
+ nativeMsg.buffer[index] & 0x0FF);
+ }
+ }
+
+ private byte[] generateRandomMessage(int len) {
+ byte[] buffer = new byte[len];
+ for(int index = 0; index < len; index++) {
+ buffer[index] = (byte)(mRandom.nextInt() & 0xFF);
+ }
+ return buffer;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Log.i(TAG, "++++ setUp() mContext:" + mContext);
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ assertTrue("FEATURE_MIDI Not Supported.", false);
+ return; // Not supported so don't test it.
+ }
+ mMidiManager = (MidiManager)mContext.getSystemService(Context.MIDI_SERVICE);
+ assertTrue("Could not get the MidiManger.", mMidiManager != null);
+
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ Log.i(TAG, "++++ tearDown()");
+ mMidiManager = null;
+ }
+
+ // Search through the available devices for the ECHO loop-back device.
+ protected MidiDeviceInfo findEchoDevice() {
+ MidiDeviceInfo[] infos = mMidiManager.getDevices();
+ MidiDeviceInfo echoInfo = null;
+ for (MidiDeviceInfo info : infos) {
+ Bundle properties = info.getProperties();
+ String manufacturer = (String) properties.get(
+ MidiDeviceInfo.PROPERTY_MANUFACTURER);
+
+ if (TEST_MANUFACTURER.equals(manufacturer)) {
+ String product = (String) properties.get(
+ MidiDeviceInfo.PROPERTY_PRODUCT);
+ if (ECHO_PRODUCT.equals(product)) {
+ echoInfo = info;
+ break;
+ }
+ }
+ }
+ assertTrue("could not find " + ECHO_PRODUCT, echoInfo != null);
+ return echoInfo;
+ }
+
+ protected long setUpEchoServer() throws Exception {
+ // Log.i(TAG, "++++ setUpEchoServer()");
+ MidiDeviceInfo echoInfo = findEchoDevice();
+
+ // Open device.
+ MyTestOpenCallback callback = new MyTestOpenCallback();
+ mMidiManager.openDevice(echoInfo, callback, null);
+ mEchoDevice = callback.waitForOpen(TIMEOUT_OPEN_MSEC);
+ assertTrue("could not open " + ECHO_PRODUCT, mEchoDevice != null);
+
+ // Query echo service directly to see if it is getting status updates.
+ NativeMidiEchoTestService echoService = NativeMidiEchoTestService.getInstance();
+
+ try {
+ mEchoDevice.mirrorToNative();
+ } catch (IOException ex) {
+ Log.i(TAG, "! Failed to mirror to native !" + ex.getMessage() + "\n");
+ assertTrue("! Failed to mirror to native !", false);
+ }
+
+ long testContext = allocTestContext();
+ assertTrue("couldn't allocate test context.", testContext != 0);
+
+ // Open Input
+ int result =
+ startWritingMidi(testContext, mEchoDevice.getInfo().getId(), 0/*mPortNumber*/);
+ assertEquals("Bad start writing (native) MIDI", 0, result);
+
+ // Open Output
+ result = startReadingMidi(testContext, mEchoDevice.getInfo().getId(), 0/*mPortNumber*/);
+ assertEquals("Bad start Reading (native) MIDI", 0, result);
+
+ return testContext;
+ }
+
+ protected void tearDownEchoServer(long testContext) throws IOException {
+ // Log.i(TAG, "++++ tearDownEchoServer()");
+ // Query echo service directly to see if it is getting status updates.
+ NativeMidiEchoTestService echoService = NativeMidiEchoTestService.getInstance();
+
+ int result;
+
+ // Stop inputs
+ result = stopReadingMidi(testContext);
+ assertEquals("Bad stop reading (native) MIDI", 0, result);
+ result = stopReadingMidi(testContext); // should be safe to close twice
+ assertTrue("Didn't get error code on second stop reading MIDI.", result != 0);
+
+ // Stop outputs
+ result = stopWritingMidi(testContext);
+ assertEquals("Bad stop writing (native) MIDI", 0, result);
+ result = stopWritingMidi(testContext); // should be safe to close twice, but get an error
+ assertTrue("Didn't get error code on second stop writing MIDI.", result != 0);
+
+ freeTestContext(testContext);
+
+ mEchoDevice.close();
+ }
+
+ // Is the MidiManager supported?
+ public void testMidiManager() throws Exception {
+ Log.i(TAG, "++++ testMidiManager() this:" + System.identityHashCode(this));
+
+ if (!hasMidiSupport()) {
+ return; // Nothing to test
+ }
+
+ assertNotNull("MidiManager not supported.", mMidiManager);
+
+ // There should be at least one device for the Echo server.
+ MidiDeviceInfo[] infos = mMidiManager.getDevices();
+ assertNotNull("device list was null", infos);
+ assertTrue("device list was empty", infos.length >= 1);
+
+ // Log.i(TAG, "++++ Num MIDI Devices:" + infos.length);
+ }
+
+ public void testSetupTeardownEchoServer() throws Exception {
+ Log.i(TAG, "++++ testSetupTeardownEchoServer() this:" + System.identityHashCode(this));
+
+ long testContext = setUpEchoServer();
+ tearDownEchoServer(testContext);
+ }
+
+ public void testSendData() throws Exception {
+ Log.i(TAG, "++++ testSendData() this:" + System.identityHashCode(this));
+ long testContext = setUpEchoServer();
+
+ clearCounters(testContext);
+
+ assertEquals("Didn't start with 0 sends", 0, getNumSends(testContext));
+ assertEquals("Didn't start with 0 bytes sent", 0, getNumBytesSent(testContext));
+
+ final byte[] buffer = {
+ (byte) 0x93, 0x47, 0x52
+ };
+ long timestamp = 0x0123765489ABFEDCL;
+ writeMidi(testContext, buffer, 0, buffer.length);
+
+ assertTrue("Didn't get 1 send", getNumBytesSent(testContext) == buffer.length);
+ assertEquals("Didn't get right number of bytes sent",
+ buffer.length, getNumBytesSent(testContext));
+
+ tearDownEchoServer(testContext);
+ }
+
+ public void testWriteGetMaxMessageSize() throws Exception {
+ if (!hasMidiSupport()) {
+ return; // nothing to test
+ }
+
+ long testContext = setUpEchoServer();
+
+ assertTrue("Invalid write buffer size", getMaxWriteBufferSize(testContext) > 0);
+ // this is based on some "inside baseball" and may well change
+ // assertEquals("write buffer size != 1015", getMaxWriteBufferSize(testContext), 1015);
+
+ tearDownEchoServer(testContext);
+ }
+
+ public void testEchoSmallMessage() throws Exception {
+ if (!hasMidiSupport()) {
+ return; // nothing to test
+ }
+
+ long testContext = setUpEchoServer();
+
+ final byte[] buffer = {
+ (byte) 0x93, 0x47, 0x52
+ };
+ long timestamp = 0x0123765489ABFEDCL;
+
+ writeMidiWithTimestamp(testContext, buffer, 0, 0, timestamp); // should be a NOOP
+ writeMidiWithTimestamp(testContext, buffer, 0, buffer.length, timestamp);
+ writeMidiWithTimestamp(testContext, buffer, 0, 0, timestamp); // should be a NOOP
+
+ // Wait for message to pass quickly through echo service.
+ final int numMessages = 1;
+ final int timeoutMs = 20;
+ Thread.sleep(timeoutMs);
+ assertEquals("number of messages.", numMessages, getNumReceivedMessages(testContext));
+
+ NativeMidiMessage message = getReceivedMessageAt(testContext, 0);
+ compareMessages(buffer, timestamp, message);
+
+ tearDownEchoServer(testContext);
+ }
+
+ public void testEchoNMessages() throws Exception {
+ if (!hasMidiSupport()) {
+ return; // nothing to test
+ }
+
+ long testContext = setUpEchoServer();
+
+ int N = 100;
+ int messageLen;
+ int maxMessageLen = getMaxWriteBufferSize(testContext);
+ byte[][] buffers = new byte[N][];
+ long timestamps[] = new long[N];
+ for(int buffIndex = 0; buffIndex < N; buffIndex++) {
+ messageLen = (int)(mRandom.nextFloat() * (maxMessageLen-1)) + 1;
+ buffers[buffIndex] = generateRandomMessage(messageLen);
+ timestamps[buffIndex] = mRandom.nextLong();
+ }
+
+ for(int msgIndex = 0; msgIndex < N; msgIndex++) {
+ writeMidiWithTimestamp(testContext, buffers[msgIndex], 0, buffers[msgIndex].length,
+ timestamps[msgIndex]);
+ }
+
+ // Wait for message to pass quickly through echo service.
+ final int timeoutMs = 20;
+ Thread.sleep(timeoutMs);
+
+ // correct number of messages
+ assertEquals("number of messages.", N, getNumReceivedMessages(testContext));
+
+ // correct data & order?
+ for(int msgIndex = 0; msgIndex < N; msgIndex++) {
+ NativeMidiMessage message = getReceivedMessageAt(testContext, msgIndex);
+ compareMessages(buffers[msgIndex], timestamps[msgIndex], message);
+ }
+
+ tearDownEchoServer(testContext);
+ }
+
+ public void testFlushMessages() throws Exception {
+ if (!hasMidiSupport()) {
+ return; // nothing to test
+ }
+
+ long testContext = setUpEchoServer();
+
+ int N = 7;
+ int messageLen;
+ int maxMessageLen = getMaxWriteBufferSize(testContext);
+ byte[][] buffers = new byte[N][];
+ long timestamps[] = new long[N];
+ for(int buffIndex = 0; buffIndex < N; buffIndex++) {
+ messageLen = (int)(mRandom.nextFloat() * (maxMessageLen-1)) + 1;
+ buffers[buffIndex] = generateRandomMessage(messageLen);
+ timestamps[buffIndex] = mRandom.nextLong();
+ }
+
+ for(int msgIndex = 0; msgIndex < N; msgIndex++) {
+ writeMidiWithTimestamp(testContext, buffers[msgIndex], 0, buffers[msgIndex].length,
+ timestamps[msgIndex]);
+ }
+
+ // Wait for message to pass quickly through echo service.
+ final int timeoutMs = 20;
+ Thread.sleep(timeoutMs);
+
+ int result = flushSentMessages(testContext);
+ assertEquals("flush messages failed", 0, result);
+
+ // correct number of messages
+ assertEquals("number of messages.", N, getNumReceivedMessages(testContext));
+
+ // correct data & order?
+ for(int msgIndex = 0; msgIndex < N; msgIndex++) {
+ NativeMidiMessage message = getReceivedMessageAt(testContext, msgIndex);
+ compareMessages(buffers[msgIndex], timestamps[msgIndex], message);
+ }
+
+ tearDownEchoServer(testContext);
+ }
+
+ public void testFailHugeMessage() throws Exception {
+ if (!hasMidiSupport()) {
+ return; // nothing to test
+ }
+
+ long testContext = setUpEchoServer();
+
+ int maxMessageLen = getMaxWriteBufferSize(testContext);
+ byte[] buffer = generateRandomMessage(maxMessageLen * 2);
+ int result = writeMidi(testContext, buffer, 0, buffer.length);
+ assertTrue("Huge write didn't fail?", result < 0);
+
+ buffer = generateRandomMessage(maxMessageLen + 1);
+ result = writeMidi(testContext, buffer, 0, buffer.length);
+ assertTrue("Kinda-big write didn't fail?", result < 0);
+
+ tearDownEchoServer(testContext);
+ }
+
+ public void testNativeEchoLatency() throws Exception {
+ if (!hasMidiSupport()) {
+ return; // nothing to test
+ }
+
+ long testContext = setUpEchoServer();
+
+ final int numMessages = 10;
+ final long maxLatencyNanos = 15 * NANOS_PER_MSEC; // generally < 3 msec on N6
+ byte[] buffer = { (byte) 0x93, 0, 64 };
+
+ // Send multiple messages in a burst.
+ for (int index = 0; index < numMessages; index++) {
+ buffer[1] = (byte) (60 + index);
+ writeMidiWithTimestamp(testContext, buffer, 0, buffer.length, System.nanoTime());
+ }
+
+ // Wait for messages to pass quickly through echo service.
+ final int timeoutMs = 20;
+ Thread.sleep(timeoutMs);
+ assertEquals("number of messages.", numMessages, getNumReceivedMessages(testContext));
+
+ for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+ NativeMidiMessage message = getReceivedMessageAt(testContext, msgIndex);
+ assertEquals("message index", (byte) (60 + msgIndex), message.buffer[1]);
+ long elapsedNanos = message.timeReceived - message.timestamp;
+ Log.i(TAG, "---- elapsed:" + elapsedNanos);
+ // If this test fails then there may be a problem with the thread scheduler
+ // or there may be kernel activity that is blocking execution at the user level.
+ assertTrue("MIDI round trip latency[" + msgIndex + "] too large, " + elapsedNanos
+ + " nanoseconds",
+ (elapsedNanos < maxLatencyNanos));
+ }
+
+ tearDownEchoServer(testContext);
+ }
+
+ public void testEchoNMessages_PureNative() throws Exception {
+ if (!hasMidiSupport()) {
+ return; // nothing to test
+ }
+
+ long testContext = setUpEchoServer();
+
+ int N = 2;
+ int messageLen;
+ int maxMessageLen = getMaxWriteBufferSize(testContext);
+ byte[][] buffers = new byte[N][];
+ long timestamps[] = new long[N];
+ for(int buffIndex = 0; buffIndex < N; buffIndex++) {
+ messageLen = (int)(mRandom.nextFloat() * (maxMessageLen-1)) + 1;
+ buffers[buffIndex] = generateRandomMessage(messageLen);
+ timestamps[buffIndex] = mRandom.nextLong();
+ }
+
+ for(int msgIndex = 0; msgIndex < N; msgIndex++) {
+ writeMidiWithTimestamp(testContext, buffers[msgIndex], 0, buffers[msgIndex].length,
+ timestamps[msgIndex]);
+ }
+
+ // Wait for message to pass quickly through echo service.
+ final int numMessages = N;
+ final int timeoutMs = 20;
+ Thread.sleep(timeoutMs);
+
+ int result = matchNativeMessages(testContext);
+ assertEquals("Native Compare Test Failed", result, 0);
+
+ tearDownEchoServer(testContext);
+ }
+
+ public void testNativeEchoLatency_PureNative() throws Exception {
+ if (!hasMidiSupport()) {
+ return; // nothing to test
+ }
+
+ long testContext = setUpEchoServer();
+
+ final int numMessages = 10;
+ final long maxLatencyNanos = 15 * NANOS_PER_MSEC; // generally < 3 msec on N6
+ byte[] buffer = { (byte) 0x93, 0, 64 };
+
+ // Send multiple messages in a burst.
+ for (int index = 0; index < numMessages; index++) {
+ buffer[1] = (byte) (60 + index);
+ writeMidiWithTimestamp(testContext, buffer, 0, buffer.length, System.nanoTime());
+ }
+
+ // Wait for messages to pass quickly through echo service.
+ final int timeoutMs = 20;
+ Thread.sleep(timeoutMs);
+ assertEquals("number of messages.", numMessages, getNumReceivedMessages(testContext));
+
+ int result = checkNativeLatency(testContext, maxLatencyNanos);
+ assertEquals("failed pure native latency test.", 0, result);
+
+ tearDownEchoServer(testContext);
+ }
+
+ // Native Routines
+ public static native void initN();
+
+ public static native long allocTestContext();
+ public static native void freeTestContext(long context);
+
+ public native int startReadingMidi(long ctx, int deviceId, int portNumber);
+ public native int stopReadingMidi(long ctx);
+
+ public native int startWritingMidi(long ctx, int deviceId, int portNumber);
+ public native int stopWritingMidi(long ctx);
+ public native int getMaxWriteBufferSize(long ctx);
+
+ public native int writeMidi(long ctx, byte[] data, int offset, int length);
+ public native int writeMidiWithTimestamp(long ctx, byte[] data, int offset, int length,
+ long timestamp);
+ public native int flushSentMessages(long ctx);
+
+ // Status - Counters
+ public native void clearCounters(long ctx);
+ public native int getNumSends(long ctx);
+ public native int getNumBytesSent(long ctx);
+ public native int getNumReceives(long ctx);
+ public native int getNumBytesReceived(long ctx);
+
+ // Status - Received Messages
+ public native int getNumReceivedMessages(long ctx);
+ public native NativeMidiMessage getReceivedMessageAt(long ctx, int index);
+
+ // Pure Native Checks
+ public native int matchNativeMessages(long ctx);
+ public native int checkNativeLatency(long ctx, long maxLatencyNanos);
+}
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTestService.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTestService.java
new file mode 100644
index 0000000..3ad9fce
--- /dev/null
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTestService.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nativemidi.cts;
+
+import android.content.Context;
+
+import android.media.midi.MidiDeviceService;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiReceiver;
+
+import java.io.IOException;
+
+import android.util.Log;
+
+/**
+ * Virtual MIDI Device that copies its input to its output.
+ * This is used for loop-back testing of MIDI I/O.
+ */
+
+public class NativeMidiEchoTestService extends MidiDeviceService {
+ private static final String TAG = "NativeMidiEchoTestService";
+
+ // Other apps will write to this port.
+ private MidiReceiver mInputReceiver = new MyReceiver();
+ // This app will copy the data to this port.
+ private MidiReceiver mOutputReceiver;
+ private static NativeMidiEchoTestService mInstance;
+
+ // These are public so we can easily read them from CTS test.
+ public int statusChangeCount;
+ public boolean inputOpened;
+ public int outputOpenCount;
+
+ @Override
+ public void onCreate() {
+ // Log.i(TAG, "++++ onCreate()");
+ super.onCreate();
+ mInstance = this;
+ MidiManager midiManager = (MidiManager)getSystemService(Context.MIDI_SERVICE);
+ // Log.i(TAG, "++++ midiManager:" + midiManager);
+ }
+
+ @Override
+ public void onDestroy() {
+ // Log.i(TAG, "++++ onDestroy()");
+ super.onDestroy();
+ }
+
+ // For CTS testing, so I can read test fields.
+ public static NativeMidiEchoTestService getInstance() {
+ // Log.i(TAG, "++++ getInstance()");
+ return mInstance;
+ }
+
+ @Override
+ public MidiReceiver[] onGetInputPortReceivers() {
+ // Log.i(TAG, "++++ onGetInputPortReceivers()");
+ return new MidiReceiver[] { mInputReceiver };
+ }
+
+ class MyReceiver extends MidiReceiver {
+ @Override
+ public void onSend(byte[] data, int offset, int count, long timestamp)
+ throws IOException {
+ if (mOutputReceiver == null) {
+ mOutputReceiver = getOutputPortReceivers()[0];
+ }
+ // Copy input to output.
+ mOutputReceiver.send(data, offset, count, timestamp);
+ }
+ }
+
+ @Override
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ statusChangeCount++;
+ inputOpened = status.isInputPortOpen(0);
+ outputOpenCount = status.getOutputPortOpenCount(0);
+ }
+}
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiMessage.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiMessage.java
new file mode 100644
index 0000000..3db1a89
--- /dev/null
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiMessage.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nativemidi.cts;
+
+//
+// Corresponds to native AMIDI_Message.
+// Used to pass message data up from the native layer to the Java test routines.
+//
+//typedef struct {
+// uint32_t opcode;
+// uint8_t buffer[AMIDI_BUFFER_SIZE];
+// size_t len;
+// uint64_t timestamp;
+//} AMIDI_Message;
+
+public class NativeMidiMessage {
+ public static final int AMIDI_PACKET_SIZE = 1024;
+ public static final int AMIDI_PACKET_OVERHEAD = 9;
+ public static final int AMIDI_BUFFER_SIZE = AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD;
+
+ public int opcode;
+ public byte[] buffer = new byte[AMIDI_BUFFER_SIZE];
+ public int len;
+ public long timestamp;
+ public long timeReceived;
+
+ public NativeMidiMessage() {}
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{opcode:" + opcode + " [len:" + len + "|");
+ for(int index = 0; index < len; index++) {
+ sb.append("0x" + Integer.toHexString(buffer[index] & 0x00FF));
+ if (index < len - 1) {
+ sb.append(" ");
+ }
+ }
+ sb.append("] timestamp:" + Long.toHexString(timestamp));
+
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/tests/tests/nativemidi/jni/Android.mk b/tests/tests/nativemidi/jni/Android.mk
new file mode 100644
index 0000000..91528bf
--- /dev/null
+++ b/tests/tests/nativemidi/jni/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE := libnativemidi_jni
+
+LOCAL_SRC_FILES := \
+ native-lib.cpp
+
+LOCAL_CFLAGS += -Wall -Wextra -Werror -O0
+
+LOCAL_C_INCLUDES += \
+ frameworks/base/media/native
+
+LOCAL_CXX_STL := libc++_static
+
+LOCAL_SHARED_LIBRARIES := libmidi
+# LOCAL_SHARED_LIBRARIES := libOpenSLES libmidi
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/nativemidi/jni/native-lib.cpp b/tests/tests/nativemidi/jni/native-lib.cpp
new file mode 100644
index 0000000..f9e88bf
--- /dev/null
+++ b/tests/tests/nativemidi/jni/native-lib.cpp
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atomic>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string>
+#include <time.h>
+#include <vector>
+
+#include <jni.h>
+
+#include <pthread.h>
+
+#include <utils/Errors.h>
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMidiManager-JNI"
+#include <utils/Log.h>
+
+#include <midi/midi.h>
+
+extern "C" {
+
+/*
+ * Structures for storing data flowing through the echo server.
+ */
+/*
+ * Received Messages
+ */
+typedef struct {
+ AMIDI_Message message;
+ long timeReceived;
+} ReceivedMessageRecord;
+
+/*
+ * Sent Messages
+ */
+typedef struct {
+ uint8_t buffer[AMIDI_BUFFER_SIZE];
+ size_t len;
+ uint64_t timestamp;
+ long timeSent;
+} SentMessageRecord;
+
+/*
+ * Context
+ * Holds the state of a given test and native MIDI I/O setup for that test.
+ */
+class TestContext {
+private:
+ // counters
+ std::atomic<int> mNumSends;
+ std::atomic<int> mNumBytesSent;
+ std::atomic<int> mNumReceives;
+ std::atomic<int> mNumBytesReceived;
+
+ pthread_mutex_t mLock;
+ std::vector<ReceivedMessageRecord> mReceivedMsgs;
+ std::vector<SentMessageRecord> mSentMsgs;
+
+ // Java NativeMidiMessage class stuff, for passing messages back out to the Java client.
+ jclass mClsNativeMidiMessage;
+ jmethodID mMidNativeMidiMessage_ctor;
+ jfieldID mFid_opcode;
+ jfieldID mFid_buffer;
+ jfieldID mFid_len;
+ jfieldID mFid_timestamp;
+ jfieldID mFid_timeReceived;
+
+public:
+ // read Thread
+ pthread_t mReadThread;
+ std::atomic<bool> mReading;
+
+ AMIDI_Device nativeReceiveDevice;
+ std::atomic<AMIDI_OutputPort> midiOutputPort;
+
+ AMIDI_Device nativeSendDevice;
+ std::atomic<AMIDI_InputPort> midiInputPort;
+
+ TestContext() :
+ mNumSends(0),
+ mNumBytesSent(0),
+ mNumReceives(0),
+ mNumBytesReceived(0),
+ mClsNativeMidiMessage(0),
+ mMidNativeMidiMessage_ctor(0),
+ mFid_opcode(0),
+ mFid_buffer(0),
+ mFid_len(0),
+ mFid_timestamp(0),
+ mFid_timeReceived(0),
+ mReadThread(0),
+ mReading(false),
+ nativeReceiveDevice(AMIDI_INVALID_HANDLE),
+ nativeSendDevice(AMIDI_INVALID_HANDLE)
+ {
+ pthread_mutex_init(&mLock, (const pthread_mutexattr_t *) NULL);
+
+ midiOutputPort.store(AMIDI_INVALID_HANDLE);
+ midiInputPort.store(AMIDI_INVALID_HANDLE);
+ }
+
+ void clearCounters() {
+ mNumSends = 0;
+ mNumBytesSent = 0;
+ mNumReceives = 0;
+ mNumBytesReceived = 0;
+ }
+
+ int getNumSends() { return mNumSends; }
+ void incNumSends() { mNumSends++; }
+
+ int getNumBytesSent() { return mNumBytesSent; }
+ void incNumBytesSent(int numBytes) { mNumBytesSent += numBytes; }
+
+ int getNumReceives() { return mNumReceives; }
+ void incNumReceives() { mNumReceives++; }
+
+ int getNumBytesReceived() { return mNumBytesReceived; }
+ void incNumBytesReceived(int numBytes) { mNumBytesReceived += numBytes; }
+
+ void addSent(SentMessageRecord msg) {
+ pthread_mutex_lock(&mLock);
+ mSentMsgs.push_back(msg);
+ pthread_mutex_unlock(&mLock);
+ }
+ size_t getNumSentMsgs() {
+ pthread_mutex_lock(&mLock);
+ size_t numMsgs = mSentMsgs.size();
+ pthread_mutex_unlock(&mLock);
+ return numMsgs;
+ }
+
+ bool initN(JNIEnv* j_env);
+
+ void addRecieved(ReceivedMessageRecord msg) {
+ pthread_mutex_lock(&mLock);
+ mReceivedMsgs.push_back(msg);
+ pthread_mutex_unlock(&mLock);
+ }
+ size_t getNumReceivedMsgs() {
+ pthread_mutex_lock(&mLock);
+ size_t numMsgs = mReceivedMsgs.size();
+ pthread_mutex_unlock(&mLock);
+ return numMsgs;
+ }
+
+ jobject j_getReceiveMsgAt(JNIEnv* j_env, int index);
+
+ static const int COMPARE_SUCCESS = 0;
+ static const int COMPARE_COUNTMISSMATCH = 1;
+ static const int COMPARE_DATALENMISMATCH = 2;
+ static const int COMPARE_DATAMISMATCH = 3;
+ static const int COMPARE_TIMESTAMPMISMATCH = 4;
+ int compareInsAndOuts();
+
+ static const int CHECKLATENCY_SUCCESS = 0;
+ static const int CHECKLATENCY_COUNTMISSMATCH = 1;
+ static const int CHECKLATENCY_LATENCYEXCEEDED = 2;
+ int checkInOutLatency(long maxLatencyNanos);
+};
+
+//
+// Helpers
+//
+static long System_nanoTime() {
+ // this code is the implementation of System.nanoTime()
+ // from system/code/ojluni/src/main/native/System.
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return now.tv_sec * 1000000000LL + now.tv_nsec;
+}
+
+bool TestContext::initN(JNIEnv* j_env) {
+ static const char* clsSigNativeMidiMessage = "android/nativemidi/cts/NativeMidiMessage";
+
+ mClsNativeMidiMessage =
+ (jclass)j_env->NewGlobalRef(j_env->FindClass(clsSigNativeMidiMessage));
+ if (mClsNativeMidiMessage == 0) {
+ return false;
+ }
+ mMidNativeMidiMessage_ctor = j_env->GetMethodID(mClsNativeMidiMessage, "<init>", "()V");
+
+ mFid_opcode = j_env->GetFieldID(mClsNativeMidiMessage, "opcode", "I");
+ mFid_buffer = j_env->GetFieldID(mClsNativeMidiMessage, "buffer", "[B");
+ mFid_len = j_env->GetFieldID(mClsNativeMidiMessage, "len", "I");
+ mFid_timestamp = j_env->GetFieldID(mClsNativeMidiMessage, "timestamp", "J");
+ mFid_timeReceived = j_env->GetFieldID(mClsNativeMidiMessage, "timeReceived", "J");
+// ALOGI("---- [%p, %p, %p, %p, %p]",
+// mFid_opcode, mFid_buffer, mFid_len, mFid_timestamp, mFid_timeReceived);
+ return mMidNativeMidiMessage_ctor != 0 &&
+ mFid_opcode != 0 &&
+ mFid_buffer != 0 &&
+ mFid_len != 0 &&
+ mFid_timestamp != 0 &&
+ mFid_timeReceived != 0;
+
+ return false;
+}
+
+jobject TestContext::j_getReceiveMsgAt(JNIEnv* j_env, int index) {
+ jobject msg = NULL;
+
+ pthread_mutex_lock(&mLock);
+
+ if (index < (int)mReceivedMsgs.size()) {
+ ReceivedMessageRecord receiveRec = mReceivedMsgs.at(index);
+ AMIDI_Message amidi_msg = receiveRec.message;
+
+ msg = j_env->NewObject(mClsNativeMidiMessage, mMidNativeMidiMessage_ctor);
+
+ j_env->SetIntField(msg, mFid_opcode, amidi_msg.opcode);
+ j_env->SetIntField(msg, mFid_len, amidi_msg.len);
+ jobject buffer_array = j_env->GetObjectField(msg, mFid_buffer);
+ j_env->SetByteArrayRegion(reinterpret_cast<jbyteArray>(buffer_array), 0,
+ sizeof(amidi_msg.buffer), (jbyte*)amidi_msg.buffer);
+ j_env->SetLongField(msg, mFid_timestamp, amidi_msg.timestamp);
+ j_env->SetLongField(msg, mFid_timeReceived, receiveRec.timeReceived);
+ }
+
+ pthread_mutex_unlock(&mLock);
+ return msg;
+}
+
+int TestContext::compareInsAndOuts() {
+ // Number of messages sent/received
+ pthread_mutex_lock(&mLock);
+
+ if (mReceivedMsgs.size() != mSentMsgs.size()) {
+ ALOGE("---- COMPARE_COUNTMISSMATCH r:%zu s:%zu", mReceivedMsgs.size(), mSentMsgs.size());
+ pthread_mutex_unlock(&mLock);
+ return COMPARE_COUNTMISSMATCH;
+ }
+
+ // we know that both vectors have the same number of messages from the test above.
+ size_t numMessages = mSentMsgs.size();
+ for (size_t msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+ // Data Length?
+ if (mReceivedMsgs[msgIndex].message.len != mSentMsgs[msgIndex].len) {
+ ALOGE("---- COMPARE_DATALENMISMATCH r:%zu s:%zu",
+ mReceivedMsgs[msgIndex].message.len, mSentMsgs[msgIndex].len);
+ pthread_mutex_unlock(&mLock);
+ return COMPARE_DATALENMISMATCH;
+ }
+
+ // Timestamps
+ if (mReceivedMsgs[msgIndex].message.timestamp != mSentMsgs[msgIndex].timestamp) {
+ ALOGE("---- COMPARE_TIMESTAMPMISMATCH");
+ pthread_mutex_unlock(&mLock);
+ return COMPARE_TIMESTAMPMISMATCH;
+ }
+
+ // we know that the data in both messages have the same number of bytes from the test above.
+ int dataLen = mReceivedMsgs[msgIndex].message.len;
+ for (int dataIndex = 0; dataIndex < dataLen; dataIndex++) {
+ // Data Values?
+ if (mReceivedMsgs[msgIndex].message.buffer[dataIndex] !=
+ mSentMsgs[msgIndex].buffer[dataIndex]) {
+ ALOGE("---- COMPARE_DATAMISMATCH r:%d s:%d",
+ (int)mReceivedMsgs[msgIndex].message.buffer[dataIndex],
+ (int)mSentMsgs[msgIndex].buffer[dataIndex]);
+ pthread_mutex_unlock(&mLock);
+ return COMPARE_DATAMISMATCH;
+ }
+ }
+ }
+
+ pthread_mutex_unlock(&mLock);
+ return COMPARE_SUCCESS;
+}
+
+int TestContext::checkInOutLatency(long maxLatencyNanos) {
+ pthread_mutex_lock(&mLock);
+ if (mReceivedMsgs.size() != mSentMsgs.size()) {
+ pthread_mutex_unlock(&mLock);
+ return CHECKLATENCY_COUNTMISSMATCH;
+ }
+
+ // we know that both vectors have the same number of messages
+ // from the test above.
+ int numMessages = mSentMsgs.size();
+ for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+ long timeDelta = mSentMsgs[msgIndex].timeSent - mReceivedMsgs[msgIndex].timeReceived;
+ // ALOGI("---- timeDelta:%ld", timeDelta);
+ if (timeDelta > maxLatencyNanos) {
+ pthread_mutex_unlock(&mLock);
+ return CHECKLATENCY_LATENCYEXCEEDED;
+ }
+ }
+
+ pthread_mutex_unlock(&mLock);
+ return CHECKLATENCY_SUCCESS;
+}
+
+JNIEXPORT jlong JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_allocTestContext(
+ JNIEnv* j_env, jclass) {
+ TestContext* context = new TestContext;
+ if (!context->initN(j_env)) {
+ delete context;
+ context = NULL;
+ }
+
+ return (jlong)context;
+}
+
+JNIEXPORT void JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_freeTestContext(
+ JNIEnv*, jclass, jlong context) {
+ delete (TestContext*)context;
+}
+
+/*
+ * Receiving API
+ */
+//static void DumpDataMessage(AMIDI_Message* msg) {
+// char midiDumpBuffer[AMIDI_BUFFER_SIZE * 4]; // more than enough
+// memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer));
+// int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
+// "%" PRIx64 " ", msg->timestamp);
+// for (uint8_t *b = msg->buffer, *e = b + msg->len; b < e; ++b) {
+// pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos,
+// "%02x ", *b);
+// }
+// ALOGI("---- DUMP %s", midiDumpBuffer);
+//}
+
+void* readThreadRoutine(void * ctx) {
+ TestContext* context = (TestContext*)ctx;
+
+ context->mReading = true;
+ while (context->mReading) {
+ AMIDI_OutputPort outputPort = context->midiOutputPort.load();
+ if (outputPort != AMIDI_INVALID_HANDLE) {
+ // Amount of messages we are ready to handle during one callback cycle.
+ static const size_t MAX_INCOMING_MIDI_MESSAGES = 20;
+ AMIDI_Message incomingMidiMessages[MAX_INCOMING_MIDI_MESSAGES];
+ ssize_t midiReceived = AMIDI_receive(
+ outputPort, incomingMidiMessages, MAX_INCOMING_MIDI_MESSAGES);
+ if (midiReceived >= 0) {
+ for (ssize_t i = 0; i < midiReceived; ++i) {
+ AMIDI_Message* msg = &incomingMidiMessages[i];
+ if (msg->opcode == AMIDI_OPCODE_DATA) {
+ // DumpDataMessage(msg);
+ context->incNumReceives();
+ context->incNumBytesReceived(msg->len);
+ ReceivedMessageRecord receiveRec;
+ receiveRec.message = *msg;
+ receiveRec.timeReceived = System_nanoTime();
+ context->addRecieved(receiveRec);
+ } else if (msg->opcode == AMIDI_OPCODE_FLUSH) {
+ ALOGI("---- FLUSH %s", "MIDI flush");
+ }
+ }
+ } else {
+ ALOGE("---- ! MIDI Receive error: %s !", strerror(-midiReceived));
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static int commonDeviceOpen(int deviceId, AMIDI_Device* device) {
+ int result = AMIDI_getDeviceByUid(deviceId, device);
+ if (result == 0) {
+ ALOGI("---- Obtained device token for uid %d: token %d", deviceId, *device);
+ } else {
+ ALOGE("---- Could not obtain device token for uid %d: result:%d", deviceId, result);
+ return result;
+ }
+
+ AMIDI_DeviceInfo deviceInfo;
+ result = AMIDI_getDeviceInfo(*device, &deviceInfo);
+ if (result == 0) {
+ ALOGI("---- Device info: uid %d, type %d, priv %d, ports %d I / %d O",
+ deviceInfo.uid, deviceInfo.type, deviceInfo.is_private,
+ (int)deviceInfo.input_port_count, (int)deviceInfo.output_port_count);
+ } else {
+ ALOGE("---- Could not obtain device info %d", result);
+ }
+
+ return result;
+}
+
+/*
+ * Sending API
+ */
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_startWritingMidi(
+ JNIEnv*, jobject, jlong ctx, jint deviceId, jint portNumber) {
+
+ TestContext* context = (TestContext*)ctx;
+
+ int result = commonDeviceOpen(deviceId, &context->nativeSendDevice);
+ if (result != 0) {
+ return result;
+ }
+
+ AMIDI_InputPort inputPort;
+ result = AMIDI_openInputPort(context->nativeSendDevice, portNumber, &inputPort);
+ if (result == 0) {
+ ALOGI("---- Opened INPUT port %d: token %d", portNumber, inputPort);
+ context->midiInputPort.store(inputPort);
+ } else {
+ ALOGE("---- Could not open INPUT port %d: %d", deviceId, result);
+ return result;
+ }
+
+ return 0;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_stopWritingMidi(
+ JNIEnv*, jobject, jlong ctx) {
+
+ TestContext* context = (TestContext*)ctx;
+
+ AMIDI_InputPort inputPort = context->midiInputPort.exchange(AMIDI_INVALID_HANDLE);
+ if (inputPort == AMIDI_INVALID_HANDLE) {
+ return android::BAD_VALUE;
+ }
+
+ int result = AMIDI_closeInputPort(inputPort);
+ if (result == 0) {
+ ALOGI("---- Closed port by token %d", inputPort);
+ } else {
+ ALOGE("---- Could not close port by token %d: %d", inputPort, result);
+ }
+
+ return result;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getMaxWriteBufferSize(
+ JNIEnv*, jobject, jlong ctx) {
+ TestContext* context = (TestContext*)ctx;
+ return AMIDI_getMaxMessageSizeInBytes(context->midiInputPort);
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidiWithTimestamp(
+ JNIEnv* j_env, jobject,
+ jlong ctx, jbyteArray data, jint offset, jint numBytes, jlong timestamp) {
+
+ TestContext* context = (TestContext*)ctx;
+ context->incNumSends();
+ context->incNumBytesSent(numBytes);
+
+ jbyte* bufferPtr = j_env->GetByteArrayElements(data, NULL);
+ if (bufferPtr == NULL) {
+ return android::NO_MEMORY;
+ }
+
+ int numWritten = AMIDI_sendWithTimestamp(
+ context->midiInputPort, (uint8_t*)bufferPtr + offset, numBytes, timestamp);
+ if (numWritten > 0) {
+ // Don't save a send record if we didn't send!
+ SentMessageRecord sendRec;
+ memcpy(sendRec.buffer, (uint8_t*)bufferPtr + offset, numBytes);
+ sendRec.len = numBytes;
+ sendRec.timestamp = timestamp;
+ sendRec.timeSent = System_nanoTime();
+ context->addSent(sendRec);
+ }
+
+ j_env->ReleaseByteArrayElements(data, bufferPtr, JNI_ABORT);
+
+ return numWritten;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidi(
+ JNIEnv* j_env, jobject j_object, jlong ctx, jbyteArray data, jint offset, jint numBytes) {
+ return Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidiWithTimestamp(
+ j_env, j_object, ctx, data, offset, numBytes, 0L);
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_flushSentMessages(
+ JNIEnv*, jobject, jlong ctx) {
+ TestContext* context = (TestContext*)ctx;
+ return AMIDI_flush(context->midiInputPort);
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumSends(
+ JNIEnv*, jobject, jlong ctx) {
+ return ((TestContext*)ctx)->getNumSends();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesSent(
+ JNIEnv*, jobject, jlong ctx) {
+ return ((TestContext*)ctx)->getNumBytesSent();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceives(
+ JNIEnv*, jobject, jlong ctx) {
+ return ((TestContext*)ctx)->getNumReceives();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesReceived(
+ JNIEnv*, jobject, jlong ctx) {
+ return ((TestContext*)ctx)->getNumBytesReceived();
+}
+
+JNIEXPORT void JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_clearCounters(
+ JNIEnv*, jobject, jlong ctx) {
+ ((TestContext*)ctx)->clearCounters();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_startReadingMidi(
+ JNIEnv*, jobject, jlong ctx, jint deviceId, jint portNumber) {
+
+ TestContext* context = (TestContext*)ctx;
+
+ int result = commonDeviceOpen(deviceId, &context->nativeReceiveDevice);
+ if (result != 0) {
+ return result;
+ }
+
+ AMIDI_OutputPort outputPort;
+ result = AMIDI_openOutputPort(context->nativeReceiveDevice, portNumber, &outputPort);
+ if (result == 0) {
+ ALOGI("---- Opened OUTPUT port %d: token %d", portNumber, outputPort);
+ context->midiOutputPort.store(outputPort);
+ } else {
+ ALOGE("---- Could not open OUTPUT port %d: %d", deviceId, result);
+ return result;
+ }
+
+ // Start read thread
+ int pthread_result =
+ pthread_create(&context->mReadThread, NULL, readThreadRoutine, (void*)context);
+ if (pthread_result != 0) {
+ ALOGE("---- pthread_create() Error %d, 0x%X", pthread_result, pthread_result);
+ }
+
+ return result;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_stopReadingMidi(
+ JNIEnv*, jobject, jlong ctx) {
+
+ TestContext* context = (TestContext*)ctx;
+ context->mReading = false;
+
+ AMIDI_OutputPort outputPort = context->midiOutputPort.exchange(AMIDI_INVALID_HANDLE);
+ if (outputPort == AMIDI_INVALID_HANDLE) {
+ return android::BAD_VALUE;
+ }
+
+ int result = AMIDI_closeOutputPort(outputPort);
+ if (result == 0) {
+ ALOGI("---- Closed OUTPUT port by token %d", outputPort);
+ } else {
+ ALOGI("---- Could not close port by token %d: %d", outputPort, result);
+ }
+
+ return result;
+}
+
+/*
+ * Messages
+ */
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceivedMessages(
+ JNIEnv*, jobject, jlong ctx) {
+ return ((TestContext*)ctx)->getNumReceivedMsgs();
+}
+
+JNIEXPORT jobject JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getReceivedMessageAt(
+ JNIEnv* j_env, jobject, jlong ctx, jint index) {
+ return ((TestContext*)ctx)->j_getReceiveMsgAt(j_env, index);
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_matchNativeMessages(
+ JNIEnv*, jobject, jlong ctx) {
+ return ((TestContext*)ctx)->compareInsAndOuts();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_checkNativeLatency(
+ JNIEnv*, jobject, jlong ctx, jlong maxLatencyNanos) {
+ return ((TestContext*)ctx)->checkInOutLatency(maxLatencyNanos);
+}
+
+} // extern "C"
diff --git a/tests/tests/nativemidi/res/xml/echo_device_info.xml b/tests/tests/nativemidi/res/xml/echo_device_info.xml
new file mode 100644
index 0000000..d3fdda0
--- /dev/null
+++ b/tests/tests/nativemidi/res/xml/echo_device_info.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<devices>
+ <device manufacturer="AndroidCTS" product="NativeMidiEcho" tags="echo,test">
+ <input-port name="input" />
+ <output-port name="output" />
+ </device>
+</devices>
diff --git a/tests/tests/net/assets/HSR1ProfileWithCACert.base64 b/tests/tests/net/assets/HSR1ProfileWithCACert.base64
new file mode 100644
index 0000000..995963d
--- /dev/null
+++ b/tests/tests/net/assets/HSR1ProfileWithCACert.base64
@@ -0,0 +1,85 @@
+Q29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5PXtib3VuZGFyeX0KQ29udGVu
+dC1UcmFuc2Zlci1FbmNvZGluZzogYmFzZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBh
+cHBsaWNhdGlvbi94LXBhc3Nwb2ludC1wcm9maWxlCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6
+IGJhc2U2NAoKUEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29n
+SUR4V1pYSkVWRVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVo
+YldVK1VHVnlVSEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0Fn
+UEZKVVVISnZjR1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhi
+V1UrZFhKdU9uZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZ
+M0pwY0hScGIyNDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThM
+MUpVVUhKdmNHVnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVSth
+VEF3TVR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFRt
+RnRaVDVJYjIxbFUxQTgKTDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lD
+QWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdApaVHd2VG05a1pVNWhiV1UrQ2lBZ0lD
+QWdJQ0FnSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0FnCklD
+QWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
+bFBrWlJSRTQ4TDA1dlpHVk8KWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBtMXBOaTVqYnk1
+MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZwpJQ0FnSUNBZ0lEeE9iMlJsUGdv
+Z0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhMMDV2
+ClpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThMMVpo
+YkhWbFBnb2dJQ0FnSUNBZ0lEd3YKVG05a1pUNEtJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0E4
+VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdQpkR2xoYkR3dlRtOWtaVTVo
+YldVK0NpQWdJQ0FnSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1VtVmhi
+RzA4CkwwNXZaR1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBuTm9ZV3RsYmk1emRHbHlj
+bVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9i
+MlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbApVR0Z6YzNkdmNtUThM
+MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2Iy
+UmxUbUZ0ClpUNVZjMlZ5Ym1GdFpUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
+eDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lD
+QWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaApjM04zYjNKa1BD
+OU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5UFR3
+dlZtRnNkV1UrCkNpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0
+S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWwKUGtWQlVFMWxkR2h2WkR3dlRtOWtaVTVoYldV
+K0NpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4TwpiMlJsVG1G
+dFpUNUZRVkJVZVhCbFBDOU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1Ur
+TWpFOEwxWmhiSFZsClBnb2dJQ0FnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0Fn
+SUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE8KYjJSbFRtRnRaVDVKYm01bGNrMWxkR2h2
+WkR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSQpRVkF0
+VmpJOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThM
+MDV2WkdVK0NpQWdJQ0FnCklDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJ
+Q0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjAKYVdacFkyRjBaVHd2VG05a1pVNWhi
+V1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbApQ
+a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
+eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
+QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
+VTJSbWx1WjJWeWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
+bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
+OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0E4
+VG05awpaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrbE5VMGs4TDA1dlpHVk9ZVzFs
+UGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzCmRXVSthVzF6YVR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0Fn
+SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWcKSUNBZ0lDQWdQRTV2
+WkdWT1lXMWxQa1ZCVUZSNWNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
+V1UrTWpROApMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEd3ZU
+bTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdQQzlPYjJSbFBnb2dJRHd2VG05a1pUNEtQ
+QzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94
+LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRMUzFD
+UlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlowbEtR
+VWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQwSm5U
+bFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYyaGpU
+azFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZrRm5V
+VEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtOblMw
+TkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIxV1ZY
+TldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJOQ01T
+dHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpVClJV
+RkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwyUzJo
+U1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dyVUdK
+ck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIzUWtz
+M2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNLMGxh
+CmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFVRzVX
+TTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNXVVZH
+U1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJOMDFF
+YlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RWSkJk
+MFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldVUldV
+akJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNXaDJZ
+MDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQwaEZD
+amxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5VVNY
+ZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5KT1Ew
+OTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhsT2JX
+MVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhnMmVF
+MXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxXYUhw
+aFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5WnpG
+S1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQTDNS
+a1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJKUmts
+RFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
diff --git a/tests/tests/net/assets/PerProviderSubscription.xml b/tests/tests/net/assets/PerProviderSubscription.xml
new file mode 100644
index 0000000..7f2d95d
--- /dev/null
+++ b/tests/tests/net/assets/PerProviderSubscription.xml
@@ -0,0 +1,399 @@
+<MgmtTree xmlns="syncml:dmddf1.2">
+ <VerDTD>1.2</VerDTD>
+ <Node>
+ <NodeName>PerProviderSubscription</NodeName>
+ <RTProperties>
+ <Type>
+ <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName>
+ </Type>
+ </RTProperties>
+ <Node>
+ <NodeName>UpdateIdentifier</NodeName>
+ <Value>12</Value>
+ </Node>
+ <Node>
+ <NodeName>i001</NodeName>
+ <Node>
+ <NodeName>HomeSP</NodeName>
+ <Node>
+ <NodeName>FriendlyName</NodeName>
+ <Value>Century House</Value>
+ </Node>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>mi6.co.uk</Value>
+ </Node>
+ <Node>
+ <NodeName>RoamingConsortiumOI</NodeName>
+ <Value>112233,445566</Value>
+ </Node>
+ <Node>
+ <NodeName>IconURL</NodeName>
+ <Value>icon.test.com</Value>
+ </Node>
+ <Node>
+ <NodeName>NetworkID</NodeName>
+ <Node>
+ <NodeName>n001</NodeName>
+ <Node>
+ <NodeName>SSID</NodeName>
+ <Value>TestSSID</Value>
+ </Node>
+ <Node>
+ <NodeName>HESSID</NodeName>
+ <Value>12345678</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>n002</NodeName>
+ <Node>
+ <NodeName>SSID</NodeName>
+ <Value>NullHESSID</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>HomeOIList</NodeName>
+ <Node>
+ <NodeName>h001</NodeName>
+ <Node>
+ <NodeName>HomeOI</NodeName>
+ <Value>11223344</Value>
+ </Node>
+ <Node>
+ <NodeName>HomeOIRequired</NodeName>
+ <Value>true</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>h002</NodeName>
+ <Node>
+ <NodeName>HomeOI</NodeName>
+ <Value>55667788</Value>
+ </Node>
+ <Node>
+ <NodeName>HomeOIRequired</NodeName>
+ <Value>false</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>OtherHomePartners</NodeName>
+ <Node>
+ <NodeName>o001</NodeName>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>other.fqdn.com</Value>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>Credential</NodeName>
+ <Node>
+ <NodeName>CreationDate</NodeName>
+ <Value>2016-01-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>ExpirationDate</NodeName>
+ <Value>2016-02-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>Realm</NodeName>
+ <Value>shaken.stirred.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CheckAAAServerCertStatus</NodeName>
+ <Value>true</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>james</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>Ym9uZDAwNw==</Value>
+ </Node>
+ <Node>
+ <NodeName>MachineManaged</NodeName>
+ <Value>true</Value>
+ </Node>
+ <Node>
+ <NodeName>SoftTokenApp</NodeName>
+ <Value>TestApp</Value>
+ </Node>
+ <Node>
+ <NodeName>AbleToShare</NodeName>
+ <Value>true</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPMethod</NodeName>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>21</Value>
+ </Node>
+ <Node>
+ <NodeName>InnerMethod</NodeName>
+ <Value>MS-CHAP-V2</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>DigitalCertificate</NodeName>
+ <Node>
+ <NodeName>CertificateType</NodeName>
+ <Value>x509v3</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SIM</NodeName>
+ <Node>
+ <NodeName>IMSI</NodeName>
+ <Value>imsi</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>24</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>Policy</NodeName>
+ <Node>
+ <NodeName>PreferredRoamingPartnerList</NodeName>
+ <Node>
+ <NodeName>p001</NodeName>
+ <Node>
+ <NodeName>FQDN_Match</NodeName>
+ <Value>test1.fqdn.com,exactMatch</Value>
+ </Node>
+ <Node>
+ <NodeName>Priority</NodeName>
+ <Value>127</Value>
+ </Node>
+ <Node>
+ <NodeName>Country</NodeName>
+ <Value>us,fr</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>p002</NodeName>
+ <Node>
+ <NodeName>FQDN_Match</NodeName>
+ <Value>test2.fqdn.com,includeSubdomains</Value>
+ </Node>
+ <Node>
+ <NodeName>Priority</NodeName>
+ <Value>200</Value>
+ </Node>
+ <Node>
+ <NodeName>Country</NodeName>
+ <Value>*</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>MinBackhaulThreshold</NodeName>
+ <Node>
+ <NodeName>m001</NodeName>
+ <Node>
+ <NodeName>NetworkType</NodeName>
+ <Value>home</Value>
+ </Node>
+ <Node>
+ <NodeName>DLBandwidth</NodeName>
+ <Value>23412</Value>
+ </Node>
+ <Node>
+ <NodeName>ULBandwidth</NodeName>
+ <Value>9823</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>m002</NodeName>
+ <Node>
+ <NodeName>NetworkType</NodeName>
+ <Value>roaming</Value>
+ </Node>
+ <Node>
+ <NodeName>DLBandwidth</NodeName>
+ <Value>9271</Value>
+ </Node>
+ <Node>
+ <NodeName>ULBandwidth</NodeName>
+ <Value>2315</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>PolicyUpdate</NodeName>
+ <Node>
+ <NodeName>UpdateInterval</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UpdateMethod</NodeName>
+ <Value>OMA-DM-ClientInitiated</Value>
+ </Node>
+ <Node>
+ <NodeName>Restriction</NodeName>
+ <Value>HomeSP</Value>
+ </Node>
+ <Node>
+ <NodeName>URI</NodeName>
+ <Value>policy.update.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>updateUser</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>updatePass</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>TrustRoot</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>update.cert.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SPExclusionList</NodeName>
+ <Node>
+ <NodeName>s001</NodeName>
+ <Node>
+ <NodeName>SSID</NodeName>
+ <Value>excludeSSID</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>RequiredProtoPortTuple</NodeName>
+ <Node>
+ <NodeName>r001</NodeName>
+ <Node>
+ <NodeName>IPProtocol</NodeName>
+ <Value>12</Value>
+ </Node>
+ <Node>
+ <NodeName>PortNumber</NodeName>
+ <Value>34,92,234</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>MaximumBSSLoadValue</NodeName>
+ <Value>23</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>CredentialPriority</NodeName>
+ <Value>99</Value>
+ </Node>
+ <Node>
+ <NodeName>AAAServerTrustRoot</NodeName>
+ <Node>
+ <NodeName>a001</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>server1.trust.root.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SubscriptionUpdate</NodeName>
+ <Node>
+ <NodeName>UpdateInterval</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UpdateMethod</NodeName>
+ <Value>SSP-ClientInitiated</Value>
+ </Node>
+ <Node>
+ <NodeName>Restriction</NodeName>
+ <Value>RoamingPartner</Value>
+ </Node>
+ <Node>
+ <NodeName>URI</NodeName>
+ <Value>subscription.update.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>subscriptionUser</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>subscriptionPass</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>TrustRoot</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>subscription.update.cert.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SubscriptionParameter</NodeName>
+ <Node>
+ <NodeName>CreationDate</NodeName>
+ <Value>2016-02-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>ExpirationDate</NodeName>
+ <Value>2016-03-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>TypeOfSubscription</NodeName>
+ <Value>Gold</Value>
+ </Node>
+ <Node>
+ <NodeName>UsageLimits</NodeName>
+ <Node>
+ <NodeName>DataLimit</NodeName>
+ <Value>921890</Value>
+ </Node>
+ <Node>
+ <NodeName>StartDate</NodeName>
+ <Value>2016-12-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>TimeLimit</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UsageTimePeriod</NodeName>
+ <Value>99910</Value>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+</MgmtTree>
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index 185ebfa..24871ca 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -18,6 +18,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -384,6 +385,57 @@
}
/**
+ * Exercises the requestNetwork with NetworkCallback API. This checks to
+ * see if we get a callback for an INTERNET request.
+ */
+ public void testRequestNetworkCallback() {
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build(), callback);
+
+ try {
+ // Wait to get callback for availability of internet
+ Network internetNetwork = callback.waitForAvailable();
+ assertNotNull("Did not receive NetworkCallback#onAvailable for INTERNET",
+ internetNetwork);
+ } catch (InterruptedException e) {
+ fail("NetworkCallback wait was interrupted.");
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ }
+ }
+
+ /**
+ * Exercises the requestNetwork with NetworkCallback API with timeout - expected to
+ * fail. Use WIFI and switch Wi-Fi off.
+ */
+ public void testRequestNetworkCallback_onUnavailable() {
+ final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
+ if (previousWifiEnabledState) {
+ disconnectFromWifi(null);
+ }
+
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build(), 100, callback);
+
+ try {
+ // Wait to get callback for unavailability of requested network
+ assertTrue("Did not receive NetworkCallback#onUnavailable",
+ callback.waitForUnavailable());
+ } catch (InterruptedException e) {
+ fail("NetworkCallback wait was interrupted.");
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ if (previousWifiEnabledState) {
+ connectToWifi();
+ }
+ }
+ }
+
+ /**
* Tests reporting of connectivity changed.
*/
public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
@@ -639,6 +691,7 @@
private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
private final CountDownLatch mLostLatch = new CountDownLatch(1);
+ private final CountDownLatch mUnavailableLatch = new CountDownLatch(1);
public Network currentNetwork;
public Network lastLostNetwork;
@@ -651,6 +704,11 @@
return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
}
+ public boolean waitForUnavailable() throws InterruptedException {
+ return mUnavailableLatch.await(2, TimeUnit.SECONDS);
+ }
+
+
@Override
public void onAvailable(Network network) {
currentNetwork = network;
@@ -665,6 +723,11 @@
}
mLostLatch.countDown();
}
+
+ @Override
+ public void onUnavailable() {
+ mUnavailableLatch.countDown();
+ }
}
private Network getWifiNetwork() {
diff --git a/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
new file mode 100644
index 0000000..bea4500
--- /dev/null
+++ b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.WifiManager;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.Characteristics;
+import android.net.wifi.aware.IdentityChangedListener;
+import android.net.wifi.aware.WifiAwareManager;
+import android.net.wifi.aware.WifiAwareSession;
+import android.test.AndroidTestCase;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wi-Fi Aware CTS test suite: single device testing. Performs tests on a single
+ * device to validate Wi-Fi Aware.
+ */
+public class SingleDeviceTest extends AndroidTestCase {
+ private static final String TAG = "WifiAwareCtsTests";
+
+ // wait for Wi-Fi Aware to become available
+ static private final int WAIT_FOR_AWARE_CHANGE_SECS = 10;
+
+ private final Object mLock = new Object();
+
+ private WifiAwareManager mWifiAwareManager;
+ private WifiManager mWifiManager;
+ private WifiManager.WifiLock mWifiLock;
+
+ // used to store any WifiAwareSession allocated during tests - will clean-up after tests
+ private List<WifiAwareSession> mSessions = new ArrayList<>();
+
+ private class WifiAwareBroadcastReceiver extends BroadcastReceiver {
+ private CountDownLatch mBlocker = new CountDownLatch(1);
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED.equals(intent.getAction())) {
+ mBlocker.countDown();
+ }
+ }
+
+ boolean waitForStateChange() throws InterruptedException {
+ return mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+ }
+ };
+
+ private class AttachCallbackTest extends AttachCallback {
+ static final int ATTACHED = 0;
+ static final int ATTACH_FAILED = 1;
+ static final int ERROR = 2; // no callback: timeout, interruption
+
+ private CountDownLatch mBlocker = new CountDownLatch(1);
+ private int mCallbackCalled = ERROR; // garbage init
+ private WifiAwareSession mSession = null;
+
+ @Override
+ public void onAttached(WifiAwareSession session) {
+ mCallbackCalled = ATTACHED;
+ mSession = session;
+ synchronized (mLock) {
+ mSessions.add(session);
+ }
+ mBlocker.countDown();
+ }
+
+ @Override
+ public void onAttachFailed() {
+ mCallbackCalled = ATTACH_FAILED;
+ mBlocker.countDown();
+ }
+
+ /**
+ * Waits for any of the callbacks to be called - or an error (timeout, interruption).
+ * Returns one of the ATTACHED, ATTACH_FAILED, or ERROR values.
+ */
+ int waitForAnyCallback() {
+ try {
+ boolean noTimeout = mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+ if (noTimeout) {
+ return mCallbackCalled;
+ } else {
+ return ERROR;
+ }
+ } catch (InterruptedException e) {
+ return ERROR;
+ }
+ }
+
+ /**
+ * Access the session created by a callback. Only useful to be called after calling
+ * waitForAnyCallback() and getting the ATTACHED code back.
+ */
+ WifiAwareSession getSession() {
+ return mSession;
+ }
+ }
+
+ private class IdentityChangedListenerTest extends IdentityChangedListener {
+ private CountDownLatch mBlocker = new CountDownLatch(1);
+ private byte[] mMac = null;
+
+ @Override
+ public void onIdentityChanged(byte[] mac) {
+ mMac = mac;
+ mBlocker.countDown();
+ }
+
+ /**
+ * Waits for the listener callback to be called - or an error (timeout, interruption).
+ * Returns true on callback called, false on error (timeout, interruption).
+ */
+ boolean waitForListener() {
+ try {
+ return mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the MAC address of the discovery interface supplied to the triggered callback.
+ */
+ byte[] getMac() {
+ return mMac;
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ mWifiAwareManager = (WifiAwareManager) getContext().getSystemService(
+ Context.WIFI_AWARE_SERVICE);
+ assertNotNull("Wi-Fi Aware Manager", mWifiAwareManager);
+
+ mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+ assertNotNull("Wi-Fi Manager", mWifiManager);
+ mWifiLock = mWifiManager.createWifiLock(TAG);
+ mWifiLock.acquire();
+ if (!mWifiManager.isWifiEnabled()) {
+ mWifiManager.setWifiEnabled(true);
+ }
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
+ WifiAwareBroadcastReceiver receiver = new WifiAwareBroadcastReceiver();
+ mContext.registerReceiver(receiver, intentFilter);
+ if (!mWifiAwareManager.isAvailable()) {
+ assertTrue("Timeout waiting for Wi-Fi Aware to change status",
+ receiver.waitForStateChange());
+ assertTrue("Wi-Fi Aware is not available (should be)", mWifiAwareManager.isAvailable());
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ super.tearDown();
+ return;
+ }
+
+ synchronized (mLock) {
+ for (WifiAwareSession session : mSessions) {
+ // no damage from destroying twice (i.e. ok if test cleaned up after itself already)
+ session.destroy();
+ }
+ mSessions.clear();
+ }
+
+ super.tearDown();
+ }
+
+ /**
+ * Validate:
+ * - Characteristics are available
+ * - Characteristics values are legitimate. Not in the CDD. However, the tested values are
+ * based on the Wi-Fi Aware protocol.
+ */
+ public void testCharacteristics() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ Characteristics characteristics = mWifiAwareManager.getCharacteristics();
+ assertNotNull("Wi-Fi Aware characteristics are null", characteristics);
+ assertEquals("Service Name Length", characteristics.getMaxServiceNameLength(), 255);
+ assertEquals("Service Specific Information Length",
+ characteristics.getMaxServiceSpecificInfoLength(), 255);
+ assertEquals("Match Filter Length", characteristics.getMaxMatchFilterLength(), 255);
+ }
+
+ /**
+ * Validate that on Wi-Fi Aware availability change we get a broadcast + the API returns
+ * correct status.
+ */
+ public void testAvailabilityStatusChange() throws Exception {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
+
+ // 1. Disable Wi-Fi
+ WifiAwareBroadcastReceiver receiver1 = new WifiAwareBroadcastReceiver();
+ mContext.registerReceiver(receiver1, intentFilter);
+ mWifiManager.setWifiEnabled(false);
+
+ assertTrue("Timeout waiting for Wi-Fi Aware to change status",
+ receiver1.waitForStateChange());
+ assertFalse("Wi-Fi Aware is available (should not be)", mWifiAwareManager.isAvailable());
+
+ // 2. Enable Wi-Fi
+ WifiAwareBroadcastReceiver receiver2 = new WifiAwareBroadcastReceiver();
+ mContext.registerReceiver(receiver2, intentFilter);
+ mWifiManager.setWifiEnabled(true);
+
+ assertTrue("Timeout waiting for Wi-Fi Aware to change status",
+ receiver2.waitForStateChange());
+ assertTrue("Wi-Fi Aware is not available (should be)", mWifiAwareManager.isAvailable());
+ }
+
+ /**
+ * Validate that can attach to Wi-Fi Aware.
+ */
+ public void testAttachNoIdentity() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ AttachCallbackTest attachCb = new AttachCallbackTest();
+ mWifiAwareManager.attach(attachCb, null);
+ int cbCalled = attachCb.waitForAnyCallback();
+ assertEquals("Wi-Fi Aware attach", AttachCallbackTest.ATTACHED, cbCalled);
+
+ WifiAwareSession session = attachCb.getSession();
+ assertNotNull("Wi-Fi Aware session", session);
+
+ session.destroy();
+ }
+
+ /**
+ * Validate that can attach to Wi-Fi Aware and get identity information. Use the identity
+ * information to validate that MAC address changes on every attach.
+ *
+ * Note: relies on no other entity using Wi-Fi Aware during the CTS test. Since if it is used
+ * then the attach/destroy will not correspond to enable/disable and will not result in a new
+ * MAC address being generated.
+ */
+ public void testAttachDiscoveryAddressChanges() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ final int numIterations = 10;
+ Set<TestUtils.MacWrapper> macs = new HashSet<>();
+
+ for (int i = 0; i < numIterations; ++i) {
+ AttachCallbackTest attachCb = new AttachCallbackTest();
+ IdentityChangedListenerTest identityL = new IdentityChangedListenerTest();
+ mWifiAwareManager.attach(attachCb, identityL, null);
+ assertEquals("Wi-Fi Aware attach: iteration " + i, AttachCallbackTest.ATTACHED,
+ attachCb.waitForAnyCallback());
+ assertTrue("Wi-Fi Aware attach: iteration " + i, identityL.waitForListener());
+
+ WifiAwareSession session = attachCb.getSession();
+ assertNotNull("Wi-Fi Aware session: iteration " + i, session);
+
+ byte[] mac = identityL.getMac();
+ assertNotNull("Wi-Fi Aware discovery MAC: iteration " + i, mac);
+
+ session.destroy();
+
+ macs.add(new TestUtils.MacWrapper(mac));
+ }
+
+ assertEquals("", numIterations, macs.size());
+ }
+}
diff --git a/tests/tests/net/src/android/net/wifi/aware/cts/TestUtils.java b/tests/tests/net/src/android/net/wifi/aware/cts/TestUtils.java
new file mode 100644
index 0000000..a12c8bb
--- /dev/null
+++ b/tests/tests/net/src/android/net/wifi/aware/cts/TestUtils.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import java.util.Arrays;
+
+/**
+ * Test utilities for Wi-Fi Aware CTS test suite.
+ */
+class TestUtils {
+ static final String TAG = "WifiAwareCtsTests";
+
+ /**
+ * Returns a flag indicating whether or not Wi-Fi Aware should be tested. Wi-Fi Aware
+ * should be tested if the feature is supported on the current device.
+ */
+ static boolean shouldTestWifiAware(Context context) {
+ final PackageManager pm = context.getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
+ }
+
+ /**
+ * Wraps a byte[] (MAC address representation). Intended to provide hash and equality operators
+ * so that the MAC address can be used in containers.
+ */
+ static class MacWrapper {
+ private byte[] mMac;
+
+ MacWrapper(byte[] mac) {
+ mMac = mac;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof MacWrapper)) {
+ return false;
+ }
+
+ MacWrapper lhs = (MacWrapper) o;
+ return Arrays.equals(mMac, lhs.mMac);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mMac);
+ }
+ }
+}
diff --git a/tests/tests/net/src/android/net/wifi/cts/ConfigParserTest.java b/tests/tests/net/src/android/net/wifi/cts/ConfigParserTest.java
new file mode 100644
index 0000000..52ed2a6
--- /dev/null
+++ b/tests/tests/net/src/android/net/wifi/cts/ConfigParserTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi.cts;
+
+import android.net.wifi.hotspot2.ConfigParser;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.test.AndroidTestCase;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+
+/**
+ * CTS tests for Hotspot 2.0 Release 1 installation file parsing API.
+ */
+public class ConfigParserTest extends AndroidTestCase {
+ /**
+ * Hotspot 2.0 Release 1 installation file that contains a Passpoint profile and a
+ * CA (Certificate Authority) X.509 certificate {@link FakeKeys#CA_CERT0}.
+ */
+ private static final String PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT =
+ "assets/HSR1ProfileWithCACert.base64";
+
+ /**
+ * Read the content of the given resource file into a String.
+ *
+ * @param filename String name of the file
+ * @return String
+ * @throws IOException
+ */
+ private String loadResourceFile(String filename) throws IOException {
+ InputStream in = getClass().getClassLoader().getResourceAsStream(filename);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ StringBuilder builder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ builder.append(line).append("\n");
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Generate a {@link PasspointConfiguration} that matches the configuration specified in the
+ * XML file {@link #PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT}.
+ *
+ * @return {@link PasspointConfiguration}
+ */
+ private PasspointConfiguration generateConfigurationFromProfile() {
+ PasspointConfiguration config = new PasspointConfiguration();
+
+ // HomeSP configuration.
+ HomeSp homeSp = new HomeSp();
+ homeSp.setFriendlyName("Century House");
+ homeSp.setFqdn("mi6.co.uk");
+ homeSp.setRoamingConsortiumOis(new long[] {0x112233L, 0x445566L});
+ config.setHomeSp(homeSp);
+
+ // Credential configuration.
+ Credential credential = new Credential();
+ credential.setRealm("shaken.stirred.com");
+ Credential.UserCredential userCredential = new Credential.UserCredential();
+ userCredential.setUsername("james");
+ userCredential.setPassword("Ym9uZDAwNw==");
+ userCredential.setEapType(21);
+ userCredential.setNonEapInnerMethod("MS-CHAP-V2");
+ credential.setUserCredential(userCredential);
+ Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+ certCredential.setCertType("x509v3");
+ byte[] certSha256Fingerprint = new byte[32];
+ Arrays.fill(certSha256Fingerprint, (byte)0x1f);
+ certCredential.setCertSha256Fingerprint(certSha256Fingerprint);
+ credential.setCertCredential(certCredential);
+ Credential.SimCredential simCredential = new Credential.SimCredential();
+ simCredential.setImsi("imsi");
+ simCredential.setEapType(24);
+ credential.setSimCredential(simCredential);
+ credential.setCaCertificate(FakeKeys.CA_CERT0);
+ config.setCredential(credential);
+ return config;
+ }
+
+ /**
+ * Verify a valid installation file is parsed successfully with the matching contents.
+ *
+ * @throws Exception
+ */
+ public void testParseConfigFile() throws Exception {
+ String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT);
+ PasspointConfiguration expectedConfig = generateConfigurationFromProfile();
+ PasspointConfiguration actualConfig =
+ ConfigParser.parsePasspointConfig(
+ "application/x-wifi-config", configStr.getBytes());
+ assertTrue(actualConfig.equals(expectedConfig));
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/net/src/android/net/wifi/cts/FakeKeys.java b/tests/tests/net/src/android/net/wifi/cts/FakeKeys.java
new file mode 100644
index 0000000..f422c2f
--- /dev/null
+++ b/tests/tests/net/src/android/net/wifi/cts/FakeKeys.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi.cts;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+/**
+ * A class containing test certificates and private keys.
+ */
+public class FakeKeys {
+ private static final String CA_CERT0_STRING = "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDKDCCAhCgAwIBAgIJAILlFdwzLVurMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV\n" +
+ "BAMTB0VBUCBDQTEwHhcNMTYwMTEyMTE1MDE1WhcNMjYwMTA5MTE1MDE1WjASMRAw\n" +
+ "DgYDVQQDEwdFQVAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n" +
+ "znAPUz26Msae4ws43czR41/J2QtrSIZUKmVUsVumDbYHrPNvTXKSMXAcewORDQYX\n" +
+ "RqvHvpn8CscB1+oGXZvHwxj4zV0WKoK2zeXkau3vcyl3HIKupJfq2TEACefVjj0t\n" +
+ "JW+X35PGWp9/H5zIUNVNVjS7Ums84IvKhRB8512PB9UyHagXYVX5GWpAcVpyfrlR\n" +
+ "FI9Qdhh+Pbk0uyktdbf/CdfgHOoebrTtwRljM0oDtX+2Cv6j0wBK7hD8pPvf1+uy\n" +
+ "GzczigAU/4Kw7eZqydf9B+5RupR+IZipX41xEiIrKRwqi517WWzXcjaG2cNbf451\n" +
+ "xpH5PnV3i1tq04jMGQUzFwIDAQABo4GAMH4wHQYDVR0OBBYEFIwX4vs8BiBcScod\n" +
+ "5noZHRM8E4+iMEIGA1UdIwQ7MDmAFIwX4vs8BiBcScod5noZHRM8E4+ioRakFDAS\n" +
+ "MRAwDgYDVQQDEwdFQVAgQ0ExggkAguUV3DMtW6swDAYDVR0TBAUwAwEB/zALBgNV\n" +
+ "HQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAFfQqOTA7Rv7K+luQ7pnas4BYwHE\n" +
+ "9GEP/uohv6KOy0TGQFbrRTjFoLVNB9BZ1ymMDZ0/TIwIUc7wi7a8t5mEqYH153wW\n" +
+ "aWooiSjyLLhuI4sNrNCOtisdBq2r2MFXt6h0mAQYOPv8R8K7/fgSxGFqzhyNmmVL\n" +
+ "1qBJldx34SpwsTALQVPb4hGwJzZfr1PcpEQx6xMnTl8xEWZE3Ms99uaUxbQqIwRu\n" +
+ "LgAOkNCmY2m89VhzaHJ1uV85AdM/tD+Ysmlnnjt9LRCejbBipjIGjOXrg1JP+lxV\n" +
+ "muM4vH+P/mlmxsPPz0d65b+EGmJZpoLkO/tdNNvCYzjJpTEWpEsO6NMhKYo=\n" +
+ "-----END CERTIFICATE-----\n";
+ public static final X509Certificate CA_CERT0 = loadCertificate(CA_CERT0_STRING);
+
+ private static final String CA_CERT1_STRING = "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDKDCCAhCgAwIBAgIJAOM5SzKO2pzCMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV\n" +
+ "BAMTB0VBUCBDQTAwHhcNMTYwMTEyMDAxMDQ3WhcNMjYwMTA5MDAxMDQ3WjASMRAw\n" +
+ "DgYDVQQDEwdFQVAgQ0EwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n" +
+ "89ug+IEKVQXnJGKg5g4uVHg6J/8iRUxR5k2eH5o03hrJNMfN2D+cBe/wCiZcnWbI\n" +
+ "GbGZACWm2nQth2wy9Zgm2LOd3b4ocrHYls3XLq6Qb5Dd7a0JKU7pdGufiNVEkrmF\n" +
+ "EB+N64wgwH4COTvCiN4erp5kyJwkfqAl2xLkZo0C464c9XoyQOXbmYD9A8v10wZu\n" +
+ "jyNsEo7Nr2USyw+qhjWSbFbEirP77Tvx+7pJQJwdtk1V9Tn73T2dGF2WHYejei9S\n" +
+ "mcWpdIUqsu9etYH+zDmtu7I1xlkwiaVsNr2+D+qaCJyOYqrDTKVNK5nmbBPXDWZc\n" +
+ "NoDbTOoqquX7xONpq9M6jQIDAQABo4GAMH4wHQYDVR0OBBYEFAZ3A2S4qJZZwuNY\n" +
+ "wkJ6mAdc0gVdMEIGA1UdIwQ7MDmAFAZ3A2S4qJZZwuNYwkJ6mAdc0gVdoRakFDAS\n" +
+ "MRAwDgYDVQQDEwdFQVAgQ0EwggkA4zlLMo7anMIwDAYDVR0TBAUwAwEB/zALBgNV\n" +
+ "HQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAHmdMwEhtys4d0E+t7owBmoVR+lU\n" +
+ "hMCcRtWs8YKX5WIM2kTweT0h/O1xwE1mWmRv/IbDAEb8od4BjAQLhIcolStr2JaO\n" +
+ "9ZzyxjOnNzqeErh/1DHDbb/moPpqfeJ8YiEz7nH/YU56Q8iCPO7TsgS0sNNE7PfN\n" +
+ "IUsBW0yHRgpQ4OxWmiZG2YZWiECRzAC0ecPzo59N5iH4vLQIMTMYquiDeMPQnn1e\n" +
+ "NDGxG8gCtDKIaS6tMg3a28MvWB094pr2ETou8O1C8Ji0Y4hE8QJmSdT7I4+GZjgW\n" +
+ "g94DZ5RiL7sdp3vC48CXOmeT61YBIvhGUsE1rPhXqkpqQ3Z3C4TFF0jXZZc=\n" +
+ "-----END CERTIFICATE-----\n";
+ public static final X509Certificate CA_CERT1 = loadCertificate(CA_CERT1_STRING);
+
+ private static final String CLIENT_CERT_STR = "-----BEGIN CERTIFICATE-----\n" +
+ "MIIE/DCCAuQCAQEwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxCzAJBgNV\n" +
+ "BAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0aW5n\n" +
+ "MB4XDTE2MDkzMDIwNTQyOFoXDTE3MDkzMDIwNTQyOFowRDELMAkGA1UEBhMCVVMx\n" +
+ "CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdU\n" +
+ "ZXN0aW5nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpmcbuaeHfnJ\n" +
+ "k+2QNvxmdVFTawyFMNk0USCq5sexscwmxbewG/Rb8YnixwJWS44v2XkSujB67z5C\n" +
+ "s2qudFEhRXKdEuC6idbAuA97KjipHh0AAniWMsyv61fvbgsUC0b0canx3LiDq81p\n" +
+ "y28NNGmAvoazLZUZ4AhBRiwYZY6FKk723gmZoGbEIeG7J1dlXPusc1662rIjz4eU\n" +
+ "zlmmlvqyHfNqnNk8L14Vug6Xh+lOEGN85xhu1YHAEKGrS89kZxs5rum/cZU8KH2V\n" +
+ "v6eKnY03kxjiVLQtnLpm/7VUEoCMGHyruRj+p3my4+DgqMsmsH52RZCBsjyGlpbU\n" +
+ "NOwOTIX6xh+Rqloduz4AnrMYYIiIw2s8g+2zJM7VbcVKx0fGS26BKdrxgrXWfmNE\n" +
+ "nR0/REQ5AxDGw0jfTUvtdTkXAf+K4MDjcNLEZ+MA4rHfAfQWZtUR5BkHCQYxNpJk\n" +
+ "pA0gyk+BpKdC4WdzI14NSWsu5sRCmBCFqH6BTOSEq/V1cNorBxNwLSSTwFFqUDqx\n" +
+ "Y5nQLXygkJf9WHZWtSKeSjtOYgilz7UKzC2s3CsjmIyGFe+SwpuHJnuE4Uc8Z5Cb\n" +
+ "bjNGHPzqL6XnmzZHJp7RF8kBdKdjGC7dCUltzOfICZeKlzOOq+Kw42T/nXjuXvpb\n" +
+ "nkXNxg741Nwd6RecykXJbseFwm3EYxkCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEA\n" +
+ "Ga1mGwI9aXkL2fTPXO9YkAPzoGeX8aeuVYSQaSkNq+5vnogYCyAt3YDHjRG+ewTT\n" +
+ "WbnPA991xRAPac+biJeXWmwvgGj0YuT7e79phAiGkTTnbAjFHGfYnBy/tI/v7btO\n" +
+ "hRNElA5yTJ1m2fVbBEKXzMR83jrT9iyI+YLRN86zUZIaC86xxSbqnrdWN2jOK6MX\n" +
+ "dS8Arp9tPQjC/4gW+2Ilxv68jiYh+5auWHQZVjppWVY//iu4mAbkq1pTwQEhZ8F8\n" +
+ "Zrmh9DHh60hLFcfSuhIAwf/NMzppwdkjy1ruKVrpijhGKGp4OWu8nvOUgHSzxc7F\n" +
+ "PwpVZ5N2Ku4L8MLO6BG2VasRJK7l17TzDXlfLZHJjkuryOFxVaQKt8ZNFgTOaCXS\n" +
+ "E+gpTLksKU7riYckoiP4+H1sn9qcis0e8s4o/uf1UVc8GSdDw61ReGM5oZEDm1u8\n" +
+ "H9x20QU6igLqzyBpqvCKv7JNgU1uB2PAODHH78zJiUfnKd1y+o+J1iWzaGj3EFji\n" +
+ "T8AXksbTP733FeFXfggXju2dyBH+Z1S5BBTEOd1brWgXlHSAZGm97MKZ94r6/tkX\n" +
+ "qfv3fCos0DKz0oV7qBxYS8wiYhzrRVxG6ITAoH8uuUVVQaZF+G4nJ2jEqNbfuKyX\n" +
+ "ATQsVNjNNlDA0J33GobPMjT326wa4YAWMx8PI5PJZ3g=\n" +
+ "-----END CERTIFICATE-----\n";
+ public static final X509Certificate CLIENT_CERT = loadCertificate(CLIENT_CERT_STR);
+
+ private static final byte[] FAKE_RSA_KEY_1 = new byte[] {
+ (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
+ (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+ (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+ (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+ (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
+ (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
+ (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
+ (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
+ (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
+ (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
+ (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
+ (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
+ (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
+ (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
+ (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
+ (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
+ (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
+ (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
+ (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
+ (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
+ (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
+ (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
+ (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
+ (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
+ (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
+ (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
+ (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
+ (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
+ (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
+ (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
+ (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
+ (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
+ (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
+ (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
+ (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
+ (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
+ (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
+ (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
+ (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
+ (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
+ (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
+ (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
+ (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
+ (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
+ (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
+ (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
+ (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
+ (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
+ (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
+ (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
+ (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
+ (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
+ (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
+ (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
+ (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
+ (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
+ (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
+ (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
+ (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
+ (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
+ (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
+ (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
+ (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
+ (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
+ (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
+ (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
+ (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
+ (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
+ (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
+ (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
+ (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
+ (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
+ (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
+ (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
+ (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
+ (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
+ (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
+ (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
+ (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
+ (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
+ (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
+ (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
+ (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
+ (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
+ (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
+ (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
+ (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
+ (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
+ (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
+ (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
+ (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
+ (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
+ (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
+ (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
+ (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
+ (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
+ (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
+ (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
+ (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
+ (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
+ (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
+ (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
+ (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
+ (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
+ (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
+ (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
+ };
+ public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
+
+ private static X509Certificate loadCertificate(String blob) {
+ try {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ InputStream stream = new ByteArrayInputStream(blob.getBytes(StandardCharsets.UTF_8));
+
+ return (X509Certificate) certFactory.generateCertificate(stream);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static PrivateKey loadPrivateRSAKey(byte[] fakeKey) {
+ try {
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey));
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+ return null;
+ }
+ }
+}
diff --git a/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java b/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java
new file mode 100644
index 0000000..5eccc0d
--- /dev/null
+++ b/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.omadm.PpsMoParser;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.test.AndroidTestCase;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CTS tests for PPS MO (PerProviderSubscription Management Object) XML string parsing API.
+ */
+public class PpsMoParserTest extends AndroidTestCase {
+ private static final String PPS_MO_XML_FILE = "assets/PerProviderSubscription.xml";
+
+ /**
+ * Read the content of the given resource file into a String.
+ *
+ * @param filename String name of the file
+ * @return String
+ * @throws IOException
+ */
+ private String loadResourceFile(String filename) throws IOException {
+ InputStream in = getClass().getClassLoader().getResourceAsStream(filename);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ StringBuilder builder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ builder.append(line).append("\n");
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Generate a {@link PasspointConfiguration} that matches the configuration specified in the
+ * XML file {@link #PPS_MO_XML_FILE}.
+ *
+ * @return {@link PasspointConfiguration}
+ */
+ private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ byte[] certFingerprint = new byte[32];
+ Arrays.fill(certFingerprint, (byte) 0x1f);
+
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.setUpdateIdentifier(12);
+ config.setCredentialPriority(99);
+
+ // AAA Server trust root.
+ Map<String, byte[]> trustRootCertList = new HashMap<>();
+ trustRootCertList.put("server1.trust.root.com", certFingerprint);
+ config.setTrustRootCertList(trustRootCertList);
+
+ // Subscription update.
+ UpdateParameter subscriptionUpdate = new UpdateParameter();
+ subscriptionUpdate.setUpdateIntervalInMinutes(120);
+ subscriptionUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_SSP);
+ subscriptionUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER);
+ subscriptionUpdate.setServerUri("subscription.update.com");
+ subscriptionUpdate.setUsername("subscriptionUser");
+ subscriptionUpdate.setBase64EncodedPassword("subscriptionPass");
+ subscriptionUpdate.setTrustRootCertUrl("subscription.update.cert.com");
+ subscriptionUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+ config.setSubscriptionUpdate(subscriptionUpdate);
+
+ // Subscription parameters.
+ config.setSubscriptionCreationTimeInMs(format.parse("2016-02-01T10:00:00Z").getTime());
+ config.setSubscriptionExpirationTimeInMs(format.parse("2016-03-01T10:00:00Z").getTime());
+ config.setSubscriptionType("Gold");
+ config.setUsageLimitDataLimit(921890);
+ config.setUsageLimitStartTimeInMs(format.parse("2016-12-01T10:00:00Z").getTime());
+ config.setUsageLimitTimeLimitInMinutes(120);
+ config.setUsageLimitUsageTimePeriodInMinutes(99910);
+
+ // HomeSP configuration.
+ HomeSp homeSp = new HomeSp();
+ homeSp.setFriendlyName("Century House");
+ homeSp.setFqdn("mi6.co.uk");
+ homeSp.setRoamingConsortiumOis(new long[] {0x112233L, 0x445566L});
+ homeSp.setIconUrl("icon.test.com");
+ Map<String, Long> homeNetworkIds = new HashMap<>();
+ homeNetworkIds.put("TestSSID", 0x12345678L);
+ homeNetworkIds.put("NullHESSID", null);
+ homeSp.setHomeNetworkIds(homeNetworkIds);
+ homeSp.setMatchAllOis(new long[] {0x11223344});
+ homeSp.setMatchAnyOis(new long[] {0x55667788});
+ homeSp.setOtherHomePartners(new String[] {"other.fqdn.com"});
+ config.setHomeSp(homeSp);
+
+ // Credential configuration.
+ Credential credential = new Credential();
+ credential.setCreationTimeInMs(format.parse("2016-01-01T10:00:00Z").getTime());
+ credential.setExpirationTimeInMs(format.parse("2016-02-01T10:00:00Z").getTime());
+ credential.setRealm("shaken.stirred.com");
+ credential.setCheckAaaServerCertStatus(true);
+ Credential.UserCredential userCredential = new Credential.UserCredential();
+ userCredential.setUsername("james");
+ userCredential.setPassword("Ym9uZDAwNw==");
+ userCredential.setMachineManaged(true);
+ userCredential.setSoftTokenApp("TestApp");
+ userCredential.setAbleToShare(true);
+ userCredential.setEapType(21);
+ userCredential.setNonEapInnerMethod("MS-CHAP-V2");
+ credential.setUserCredential(userCredential);
+ Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+ certCredential.setCertType("x509v3");
+ certCredential.setCertSha256Fingerprint(certFingerprint);
+ credential.setCertCredential(certCredential);
+ Credential.SimCredential simCredential = new Credential.SimCredential();
+ simCredential.setImsi("imsi");
+ simCredential.setEapType(24);
+ credential.setSimCredential(simCredential);
+ config.setCredential(credential);
+
+ // Policy configuration.
+ Policy policy = new Policy();
+ List<Policy.RoamingPartner> preferredRoamingPartnerList = new ArrayList<>();
+ Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+ partner1.setFqdn("test1.fqdn.com");
+ partner1.setFqdnExactMatch(true);
+ partner1.setPriority(127);
+ partner1.setCountries("us,fr");
+ Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+ partner2.setFqdn("test2.fqdn.com");
+ partner2.setFqdnExactMatch(false);
+ partner2.setPriority(200);
+ partner2.setCountries("*");
+ preferredRoamingPartnerList.add(partner1);
+ preferredRoamingPartnerList.add(partner2);
+ policy.setPreferredRoamingPartnerList(preferredRoamingPartnerList);
+ policy.setMinHomeDownlinkBandwidth(23412);
+ policy.setMinHomeUplinkBandwidth(9823);
+ policy.setMinRoamingDownlinkBandwidth(9271);
+ policy.setMinRoamingUplinkBandwidth(2315);
+ policy.setExcludedSsidList(new String[] {"excludeSSID"});
+ Map<Integer, String> requiredProtoPortMap = new HashMap<>();
+ requiredProtoPortMap.put(12, "34,92,234");
+ policy.setRequiredProtoPortMap(requiredProtoPortMap);
+ policy.setMaximumBssLoadValue(23);
+ UpdateParameter policyUpdate = new UpdateParameter();
+ policyUpdate.setUpdateIntervalInMinutes(120);
+ policyUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_OMADM);
+ policyUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_HOMESP);
+ policyUpdate.setServerUri("policy.update.com");
+ policyUpdate.setUsername("updateUser");
+ policyUpdate.setBase64EncodedPassword("updatePass");
+ policyUpdate.setTrustRootCertUrl("update.cert.com");
+ policyUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+ policy.setPolicyUpdate(policyUpdate);
+ config.setPolicy(policy);
+ return config;
+ }
+
+ /**
+ * Parse and verify all supported fields under PPS MO tree.
+ *
+ * @throws Exception
+ */
+ public void testParsePPSMOTree() throws Exception {
+ String ppsMoTree = loadResourceFile(PPS_MO_XML_FILE);
+ PasspointConfiguration expectedConfig = generateConfigurationFromPPSMOTree();
+ PasspointConfiguration actualConfig = PpsMoParser.parseMoText(ppsMoTree);
+ assertTrue(actualConfig.equals(expectedConfig));
+ }
+}
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 4185189..d3dc8fa 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -29,6 +29,9 @@
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.TxPacketCountListener;
import android.net.wifi.WifiManager.WifiLock;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
import android.os.SystemClock;
import android.provider.Settings;
import android.test.AndroidTestCase;
@@ -38,6 +41,8 @@
import java.net.HttpURLConnection;
import java.net.URL;
+import java.security.MessageDigest;
+import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -543,4 +548,130 @@
}
assertTrue(i < 15);
}
+
+ /**
+ * Verify Passpoint configuration management APIs (add, remove, get) for a Passpoint
+ * configuration with an user credential.
+ *
+ * @throws Exception
+ */
+ public void testAddPasspointConfigWithUserCredential() throws Exception {
+ testAddPasspointConfig(generatePasspointConfig(generateUserCredential()));
+ }
+
+ /**
+ * Verify Passpoint configuration management APIs (add, remove, get) for a Passpoint
+ * configuration with a certificate credential.
+ *
+ * @throws Exception
+ */
+ public void testAddPasspointConfigWithCertCredential() throws Exception {
+ testAddPasspointConfig(generatePasspointConfig(generateCertCredential()));
+ }
+
+ /**
+ * Verify Passpoint configuration management APIs (add, remove, get) for a Passpoint
+ * configuration with a SIm credential.
+ *
+ * @throws Exception
+ */
+ public void testAddPasspointConfigWithSimCredential() throws Exception {
+ testAddPasspointConfig(generatePasspointConfig(generateSimCredential()));
+ }
+
+ /**
+ * Helper function for generating a {@link PasspointConfiguration} for testing.
+ *
+ * @return {@link PasspointConfiguration}
+ */
+ private PasspointConfiguration generatePasspointConfig(Credential credential) {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.setCredential(credential);
+
+ // Setup HomeSp.
+ HomeSp homeSp = new HomeSp();
+ homeSp.setFqdn("Test.com");
+ homeSp.setFriendlyName("Test Provider");
+ config.setHomeSp(homeSp);
+
+ return config;
+ }
+
+ /**
+ * Helper function for generating an user credential for testing.
+ *
+ * @return {@link Credential}
+ */
+ private Credential generateUserCredential() {
+ Credential credential = new Credential();
+ credential.setRealm("test.net");
+ Credential.UserCredential userCred = new Credential.UserCredential();
+ userCred.setEapType(21 /* EAP_TTLS */);
+ userCred.setUsername("username");
+ userCred.setPassword("password");
+ userCred.setNonEapInnerMethod("PAP");
+ credential.setUserCredential(userCred);
+ credential.setCaCertificate(FakeKeys.CA_CERT0);
+ return credential;
+ }
+
+ /**
+ * Helper function for generating a certificate credential for testing.
+ *
+ * @return {@link Credential}
+ */
+ private Credential generateCertCredential() throws Exception {
+ Credential credential = new Credential();
+ credential.setRealm("test.net");
+ Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+ certCredential.setCertType("x509v3");
+ certCredential.setCertSha256Fingerprint(
+ MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+ credential.setCertCredential(certCredential);
+ credential.setCaCertificate(FakeKeys.CA_CERT0);
+ credential.setClientCertificateChain(new X509Certificate[] {FakeKeys.CLIENT_CERT});
+ credential.setClientPrivateKey(FakeKeys.RSA_KEY1);
+ return credential;
+ }
+
+ /**
+ * Helper function for generating a SIM credential for testing.
+ *
+ * @return {@link Credential}
+ */
+ private Credential generateSimCredential() throws Exception {
+ Credential credential = new Credential();
+ credential.setRealm("test.net");
+ Credential.SimCredential simCredential = new Credential.SimCredential();
+ simCredential.setImsi("1234*");
+ simCredential.setEapType(18 /* EAP_SIM */);
+ credential.setSimCredential(simCredential);
+ return credential;
+ }
+
+ /**
+ * Helper function verifying Passpoint configuration management APIs (add, remove, get) for
+ * a given configuration.
+ *
+ * @param config The configuration to test with
+ */
+ private void testAddPasspointConfig(PasspointConfiguration config) throws Exception {
+ assertTrue(mWifiManager.addOrUpdatePasspointConfiguration(config));
+
+ // Certificates and keys will be set to null after it is installed to the KeyStore by
+ // WifiManager. Reset them in the expected config so that it can be used to compare
+ // against the retrieved config.
+ config.getCredential().setCaCertificate(null);
+ config.getCredential().setClientCertificateChain(null);
+ config.getCredential().setClientPrivateKey(null);
+
+ // Retrieve the configuration and verify it.
+ List<PasspointConfiguration> configList = mWifiManager.getPasspointConfigurations();
+ assertEquals(1, configList.size());
+ assertEquals(config, configList.get(0));
+
+ // Remove the configuration and verify no installed configuration.
+ assertTrue(mWifiManager.removePasspointConfiguration(config.getHomeSp().getFqdn()));
+ assertTrue(mWifiManager.getPasspointConfigurations().isEmpty());
+ }
}
diff --git a/tests/tests/preference2/src/android/preference2/cts/PreferenceDataStoreTest.java b/tests/tests/preference2/src/android/preference2/cts/PreferenceDataStoreTest.java
index 5c3b181..69456cf 100644
--- a/tests/tests/preference2/src/android/preference2/cts/PreferenceDataStoreTest.java
+++ b/tests/tests/preference2/src/android/preference2/cts/PreferenceDataStoreTest.java
@@ -17,6 +17,7 @@
package android.preference2.cts;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.AdditionalMatchers.or;
import static org.mockito.Matchers.any;
@@ -35,8 +36,10 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.Context;
+import android.content.SharedPreferences;
import android.preference.Preference;
import android.preference.PreferenceDataStore;
+import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -47,6 +50,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.internal.util.ObjectMethodsGuru;
import java.util.HashSet;
import java.util.Set;
@@ -57,9 +61,14 @@
private PreferenceWrapper mPreference;
private PreferenceDataStore mDataStore;
+ private PreferenceScreen mScreen;
+ private PreferenceManager mManager;
+ private SharedPreferences mSharedPref;
private static final String KEY = "TestPrefKey";
private static final String TEST_STR = "Test";
+ private static final String TEST_DEFAULT_STR = "TestDefault";
+ private static final String TEST_WRONG_STR = "TestFromSharedPref";
@Rule
public ActivityTestRule<PreferenceFragmentActivity> mActivityRule =
@@ -73,188 +82,30 @@
mPreference.setKey(KEY);
// Assign the Preference to the PreferenceFragment.
- PreferenceScreen screen =
- activity.prefFragment.getPreferenceManager().createPreferenceScreen(activity);
- screen.addPreference(mPreference);
+ mScreen = activity.prefFragment.getPreferenceManager().createPreferenceScreen(activity);
+ mManager = mScreen.getPreferenceManager();
+ mSharedPref = mManager.getSharedPreferences();
mDataStore = mock(PreferenceDataStore.class);
+
+ // Make sure that the key is not present in SharedPreferences to ensure test correctness.
+ mManager.getSharedPreferences().edit().remove(KEY).commit();
}
@Test
public void testPutStringWithDataStoreOnPref() {
mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
putStringTestCommon();
}
@Test
public void testPutStringWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
putStringTestCommon();
}
- @Test
- public void testGetStringWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- mPreference.getString(TEST_STR);
- verify(mDataStore, atLeastOnce()).getString(eq(KEY), eq(TEST_STR));
- }
-
- @Test
- public void testGetStringWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- mPreference.getString(TEST_STR);
- verify(mDataStore, atLeastOnce()).getString(eq(KEY), eq(TEST_STR));
- }
-
- @Test
- public void testPutStringSetWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- putStringSetTestCommon();
- }
-
- @Test
- public void testPutStringSetWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- putStringSetTestCommon();
- }
-
- @Test
- public void testGetStringSetWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- Set<String> testSet = new HashSet<>();
- mPreference.getStringSet(testSet);
- verify(mDataStore, atLeastOnce()).getStringSet(eq(KEY), eq(testSet));
- }
-
- @Test
- public void testGetStringSetWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- Set<String> testSet = new HashSet<>();
- mPreference.getStringSet(testSet);
- verify(mDataStore, atLeastOnce()).getStringSet(eq(KEY), eq(testSet));
- }
-
- @Test
- public void testPutIntWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- putIntTestCommon();
- }
-
- @Test
- public void testPutIntWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- putIntTestCommon();
- }
-
- @Test
- public void testGetIntWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- mPreference.getInt(1);
- verify(mDataStore, atLeastOnce()).getInt(eq(KEY), eq(1));
- }
-
- @Test
- public void testGetIntWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- mPreference.getInt(1);
- verify(mDataStore, atLeastOnce()).getInt(eq(KEY), eq(1));
- }
-
- @Test
- public void testPutLongWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- putLongTestCommon();
- }
-
- @Test
- public void testPutLongWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- putLongTestCommon();
- }
-
- @Test
- public void testGetLongWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- mPreference.getLong(1L);
- verify(mDataStore, atLeastOnce()).getLong(eq(KEY), eq(1L));
- }
-
- @Test
- public void testGetLongWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- mPreference.getLong(1L);
- verify(mDataStore, atLeastOnce()).getLong(eq(KEY), eq(1L));
- }
-
- @Test
- public void testPutFloatWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- putFloatTestCommon();
- }
-
- @Test
- public void testPutFloatWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- putFloatTestCommon();
- }
-
- @Test
- public void testGetFloatWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- mPreference.getFloat(1f);
- verify(mDataStore, atLeastOnce()).getFloat(eq(KEY), eq(1f));
- }
-
- @Test
- public void testGetFloatWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- mPreference.getFloat(1f);
- verify(mDataStore, atLeastOnce()).getFloat(eq(KEY), eq(1f));
- }
-
- @Test
- public void testPutBooleanWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- putBooleanTestCommon();
- }
-
- @Test
- public void testPutBooleanWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- putBooleanTestCommon();
- }
-
- @Test
- public void testGetBooleanWithDataStoreOnPref() {
- mPreference.setPreferenceDataStore(mDataStore);
- mPreference.getBoolean(true);
- verify(mDataStore, atLeastOnce()).getBoolean(eq(KEY), eq(true));
- }
-
- @Test
- public void testGetBooleanWithDataStoreOnMgr() {
- mPreference.getPreferenceManager().setPreferenceDataStore(mDataStore);
- mPreference.getBoolean(true);
- verify(mDataStore, atLeastOnce()).getBoolean(eq(KEY), eq(true));
- }
-
- @Test
- public void testDataStoresHierarchy() {
- mPreference.setPreferenceDataStore(mDataStore);
- PreferenceDataStore secondaryDataStore = mock(PreferenceDataStore.class);
- mPreference.getPreferenceManager().setPreferenceDataStore(secondaryDataStore);
- mPreference.putString(TEST_STR);
-
- // Check that the Preference returns the correct data store.
- assertEquals(mDataStore, mPreference.getPreferenceDataStore());
-
- // Check that the secondary data store assigned to the manager was NOT used.
- verifyZeroInteractions(secondaryDataStore);
-
- // Check that the primary data store assigned directly to the preference was used.
- verify(mDataStore, atLeast(0)).getString(eq(KEY), anyString());
- }
-
private void putStringTestCommon() {
mPreference.putString(TEST_STR);
@@ -263,7 +114,54 @@
verifyNoMoreInteractions(mDataStore);
// Test that the value was NOT propagated to SharedPreferences.
- assertNull(mPreference.getSharedPreferences().getString(KEY, null));
+ assertNull(mSharedPref.getString(KEY, null));
+ }
+
+ @Test
+ public void testGetStringWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getString(TEST_STR);
+ verify(mDataStore, atLeastOnce()).getString(eq(KEY), eq(TEST_STR));
+ }
+
+ @Test
+ public void testGetStringWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getString(TEST_STR);
+ verify(mDataStore, atLeastOnce()).getString(eq(KEY), eq(TEST_STR));
+ }
+
+ /**
+ * This test makes sure that when a default value is set to a preference that has a data store
+ * assigned that the default value is correctly propagated to
+ * {@link Preference#onSetInitialValue(boolean, Object)} instead of passing a value from
+ * {@link android.content.SharedPreferences}. We have this test only for String because the
+ * implementation is not dependent on value type so this coverage should be fine.
+ */
+ @Test
+ public void testDefaultStringValue() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mPreference.setDefaultValue(TEST_DEFAULT_STR);
+ mSharedPref.edit().putString(KEY, TEST_WRONG_STR).commit();
+ mScreen.addPreference(mPreference);
+ mSharedPref.edit().remove(KEY).commit();
+ assertEquals(TEST_DEFAULT_STR, mPreference.defaultValue);
+ }
+
+ @Test
+ public void testPutStringSetWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putStringSetTestCommon();
+ }
+
+ @Test
+ public void testPutStringSetWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putStringSetTestCommon();
}
private void putStringSetTestCommon() {
@@ -276,7 +174,39 @@
verifyNoMoreInteractions(mDataStore);
// Test that the value was NOT propagated to SharedPreferences.
- assertNull(mPreference.getSharedPreferences().getStringSet(KEY, null));
+ assertNull(mSharedPref.getStringSet(KEY, null));
+ }
+
+ @Test
+ public void testGetStringSetWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ Set<String> testSet = new HashSet<>();
+ mPreference.getStringSet(testSet);
+ verify(mDataStore, atLeastOnce()).getStringSet(eq(KEY), eq(testSet));
+ }
+
+ @Test
+ public void testGetStringSetWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ Set<String> testSet = new HashSet<>();
+ mPreference.getStringSet(testSet);
+ verify(mDataStore, atLeastOnce()).getStringSet(eq(KEY), eq(testSet));
+ }
+
+ @Test
+ public void testPutIntWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putIntTestCommon();
+ }
+
+ @Test
+ public void testPutIntWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putIntTestCommon();
}
private void putIntTestCommon() {
@@ -287,7 +217,37 @@
verifyNoMoreInteractions(mDataStore);
// Test that the value was NOT propagated to SharedPreferences.
- assertEquals(-1, mPreference.getSharedPreferences().getInt(KEY, -1));
+ assertEquals(-1, mSharedPref.getInt(KEY, -1));
+ }
+
+ @Test
+ public void testGetIntWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getInt(1);
+ verify(mDataStore, atLeastOnce()).getInt(eq(KEY), eq(1));
+ }
+
+ @Test
+ public void testGetIntWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getInt(1);
+ verify(mDataStore, atLeastOnce()).getInt(eq(KEY), eq(1));
+ }
+
+ @Test
+ public void testPutLongWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putLongTestCommon();
+ }
+
+ @Test
+ public void testPutLongWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putLongTestCommon();
}
private void putLongTestCommon() {
@@ -298,7 +258,37 @@
verifyNoMoreInteractions(mDataStore);
// Test that the value was NOT propagated to SharedPreferences.
- assertEquals(-1, mPreference.getSharedPreferences().getLong(KEY, -1L));
+ assertEquals(-1, mSharedPref.getLong(KEY, -1L));
+ }
+
+ @Test
+ public void testGetLongWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getLong(1L);
+ verify(mDataStore, atLeastOnce()).getLong(eq(KEY), eq(1L));
+ }
+
+ @Test
+ public void testGetLongWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getLong(1L);
+ verify(mDataStore, atLeastOnce()).getLong(eq(KEY), eq(1L));
+ }
+
+ @Test
+ public void testPutFloatWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putFloatTestCommon();
+ }
+
+ @Test
+ public void testPutFloatWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putFloatTestCommon();
}
private void putFloatTestCommon() {
@@ -309,7 +299,37 @@
verifyNoMoreInteractions(mDataStore);
// Test that the value was NOT propagated to SharedPreferences.
- assertEquals(-1, mPreference.getSharedPreferences().getFloat(KEY, -1f), 0.1f /* epsilon */);
+ assertEquals(-1, mSharedPref.getFloat(KEY, -1f), 0.1f /* epsilon */);
+ }
+
+ @Test
+ public void testGetFloatWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getFloat(1f);
+ verify(mDataStore, atLeastOnce()).getFloat(eq(KEY), eq(1f));
+ }
+
+ @Test
+ public void testGetFloatWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getFloat(1f);
+ verify(mDataStore, atLeastOnce()).getFloat(eq(KEY), eq(1f));
+ }
+
+ @Test
+ public void testPutBooleanWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putBooleanTestCommon();
+ }
+
+ @Test
+ public void testPutBooleanWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ putBooleanTestCommon();
}
private void putBooleanTestCommon() {
@@ -320,7 +340,63 @@
verifyNoMoreInteractions(mDataStore);
// Test that the value was NOT propagated to SharedPreferences.
- assertEquals(false, mPreference.getSharedPreferences().getBoolean(KEY, false));
+ assertEquals(false, mSharedPref.getBoolean(KEY, false));
+ }
+
+ @Test
+ public void testGetBooleanWithDataStoreOnPref() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getBoolean(true);
+ verify(mDataStore, atLeastOnce()).getBoolean(eq(KEY), eq(true));
+ }
+
+ @Test
+ public void testGetBooleanWithDataStoreOnMgr() {
+ mManager.setPreferenceDataStore(mDataStore);
+ mScreen.addPreference(mPreference);
+ mPreference.getBoolean(true);
+ verify(mDataStore, atLeastOnce()).getBoolean(eq(KEY), eq(true));
+ }
+
+ @Test
+ public void testDataStoresHierarchy() {
+ mPreference.setPreferenceDataStore(mDataStore);
+ PreferenceDataStore secondaryDataStore = mock(PreferenceDataStore.class);
+ mManager.setPreferenceDataStore(secondaryDataStore);
+ mPreference.putString(TEST_STR);
+
+ // Check that the Preference returns the correct data store.
+ assertEquals(mDataStore, mPreference.getPreferenceDataStore());
+
+ // Check that the secondary data store assigned to the manager was NOT used.
+ verifyZeroInteractions(secondaryDataStore);
+
+ // Check that the primary data store assigned directly to the preference was used.
+ verify(mDataStore, atLeast(0)).getString(eq(KEY), anyString());
+ }
+
+ /**
+ * When {@link PreferenceDataStore} is NOT assigned, the getter for SharedPreferences should not
+ * return null.
+ */
+ @Test
+ public void testSharedPrefNotNullIfNoDS() {
+ mScreen.addPreference(mPreference);
+ assertNotNull(mPreference.getSharedPreferences());
+ assertNotNull(mPreference.getEditor());
+ }
+
+ /**
+ * When {@link PreferenceDataStore} is assigned, the getter for SharedPreferences have to return
+ * null.
+ */
+ @Test
+ public void testSharedPrefNullIfNoDS() {
+ mScreen.addPreference(mPreference);
+ mPreference.setPreferenceDataStore(mDataStore);
+ assertNull(mPreference.getSharedPreferences());
+ assertNull(mPreference.getEditor());
}
/**
@@ -328,6 +404,8 @@
*/
private static class PreferenceWrapper extends Preference {
+ Object defaultValue;
+
PreferenceWrapper(Context context) {
super(context);
}
@@ -379,6 +457,12 @@
boolean getBoolean(boolean defaultValue) {
return getPersistedBoolean(defaultValue);
}
+
+ @Override
+ protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+ this.defaultValue = defaultValue;
+ super.onSetInitialValue(restorePersistedValue, defaultValue);
+ }
}
}
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java b/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
index 6c93d9e..7bb5908 100644
--- a/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
+++ b/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
@@ -18,6 +18,7 @@
import android.content.ContentResolver;
+import android.database.Cursor;
import android.net.Uri;
import android.provider.Settings;
import android.provider.Settings.Secure;
@@ -156,4 +157,35 @@
assertEquals("install_non_market_apps is deprecated. Should be set to 1 by default.",
1, Settings.Secure.getInt(cr, Settings.Global.INSTALL_NON_MARKET_APPS));
}
+
+ private static final String BLUETOOTH_MAC_ADDRESS_SETTING_NAME = "bluetooth_address";
+
+ /**
+ * Asserts that the secure setting containing the Android's Bluetooth MAC address is not
+ * available to non-privileged apps, such as the CTS test app in the context of which this test
+ * runs.
+ */
+ public void testBluetoothAddressNotAvailable() {
+ assertNull(Settings.Secure.getString(cr, BLUETOOTH_MAC_ADDRESS_SETTING_NAME));
+
+ // Assert this setting is not accessible when listing all settings
+ try (Cursor c = cr.query(Settings.Secure.CONTENT_URI, null, null, null, null)) {
+ while ((c != null) && (c.moveToNext())) {
+ String name = c.getString(1);
+ if (BLUETOOTH_MAC_ADDRESS_SETTING_NAME.equals(name)) {
+ fail("Settings.Secure contains " + name + ": " + c.getString(2));
+ }
+ }
+ }
+
+ // Assert this setting is not accessible when listing this specific setting
+ Uri settingUri =
+ Settings.Secure.CONTENT_URI.buildUpon().appendPath("bluetooth_address").build();
+ try (Cursor c = cr.query(settingUri, null, null, null, null)) {
+ while ((c != null) && (c.moveToNext())) {
+ String name = c.getString(1);
+ fail("Settings.Secure contains " + name + ": " + c.getString(2));
+ }
+ }
+ }
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
index 4257067..329031a 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
@@ -180,6 +180,7 @@
Log.i(LOG_TAG, "Service has been unbound");
sServiceUnBoundLatch.countDown();
sIsBound = false;
+ sConnectionService = null;
return super.onUnbind(intent);
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
index ee2f842..4185318 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
@@ -94,12 +94,14 @@
}
@Override
- public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
+ public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerHandle,
+ ConnectionRequest request) {
mLocks[CREATE_INCOMING_CONNECTION_FAILED_LOCK].countDown();
}
@Override
- public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+ public void onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerHandle,
+ ConnectionRequest request) {
mLocks[CREATE_OUTGOING_CONNECTION_FAILED_LOCK].countDown();
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
index 084c420..5ace9ff 100644
--- a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
@@ -27,6 +27,7 @@
import java.util.function.Predicate;
import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
+import static android.telecom.cts.TestUtils.waitOnAllHandlers;
/**
* CTS tests for the self-managed {@link android.telecom.ConnectionService} APIs.
@@ -37,7 +38,6 @@
public class SelfManagedConnectionServiceTest extends BaseTelecomTestWithMockServices {
private Uri TEST_ADDRESS_1 = Uri.fromParts("sip", "call1@test.com", null);
private Uri TEST_ADDRESS_2 = Uri.fromParts("sip", "call2@test.com", null);
- private Uri TEST_ADDRESS_3 = Uri.fromParts("sip", "call3@test.com", null);
@Override
protected void setUp() throws Exception {
@@ -197,7 +197,6 @@
// Expect there to be no managed calls at the moment.
assertFalse(mTelecomManager.isInManagedCall());
- assertMockInCallServiceUnbound();
setDisconnectedAndVerify(connection);
}
@@ -226,7 +225,6 @@
// Expect there to be no managed calls at the moment.
assertFalse(mTelecomManager.isInManagedCall());
- assertMockInCallServiceUnbound();
setDisconnectedAndVerify(connection);
}
@@ -346,6 +344,8 @@
connections.forEach((selfManagedConnection) ->
selfManagedConnection.disconnectAndDestroy());
+
+ waitOnAllHandlers(getInstrumentation());
}
public void testEmergencyCallOngoing() throws Exception {
diff --git a/tests/tests/telecom/src/android/telecom/cts/VideoCallTest.java b/tests/tests/telecom/src/android/telecom/cts/VideoCallTest.java
index 9f79628..7f2b27b 100644
--- a/tests/tests/telecom/src/android/telecom/cts/VideoCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/VideoCallTest.java
@@ -444,6 +444,16 @@
Connection.VideoProvider.SESSION_EVENT_RX_RESUME);
}
});
+
+ assertCallSessionEventReceived(inCallService.getVideoCallCallback(call),
+ VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR,
+ new Work() {
+ @Override
+ public void doWork() {
+ connection.sendMockCallSessionEvent(
+ Connection.VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR);
+ }
+ });
}
/**
diff --git a/tests/tests/telephony/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java b/tests/tests/telephony/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
index 62f04c9..88475d1 100644
--- a/tests/tests/telephony/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
@@ -154,7 +154,7 @@
new ShortCodeTest("ch", "123", CATEGORY_NOT_SHORT_CODE),
new ShortCodeTest("ch", "234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
new ShortCodeTest("ch", "3456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
- new ShortCodeTest("ch", "98765", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ch", "98765", CATEGORY_FREE_SHORT_CODE),
new ShortCodeTest("ch", "543", CATEGORY_PREMIUM_SHORT_CODE),
new ShortCodeTest("ch", "83111", CATEGORY_PREMIUM_SHORT_CODE),
new ShortCodeTest("ch", "234567", CATEGORY_NOT_SHORT_CODE),
diff --git a/tests/tests/util/src/android/util/cts/ArrayMapTest.java b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
index ec17516..1b07f24 100644
--- a/tests/tests/util/src/android/util/cts/ArrayMapTest.java
+++ b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
@@ -16,6 +16,11 @@
package android.util.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.os.Bundle;
@@ -31,8 +36,12 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.AbstractMap;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@@ -535,4 +544,193 @@
}
checkEntrySetToArray(testMap);
}
+
+ /**
+ * The entrySet Iterator allows iteration past its end without throwing
+ * NoSuchElementException. The Entry returned by {@link Iterator#next()}
+ * removes null key / value, but {@link Iterator#remove()} removes the
+ * last actual entry in the map.
+ * This is unusual behavior for {@link Iterator#next()}; this test ensures that
+ * any future change to this behavior is deliberate.
+ */
+ @Test
+ public void testUnusualBehavior_canIteratePastEnd_entrySetIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Set<Map.Entry<String, String>> expectedEntriesToIterate = new HashSet<>(Arrays.asList(
+ entryOf("key 1", "value 1"),
+ entryOf("key 2", "value 2")
+ ));
+ Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
+
+ // Assert iteration over the expected two entries in any order
+ assertTrue(iterator.hasNext());
+ Map.Entry<String, String> firstEntry = copyOf(iterator.next());
+ assertTrue(expectedEntriesToIterate.remove(firstEntry));
+
+ assertTrue(iterator.hasNext());
+ Map.Entry<String, String> secondEntry = copyOf(iterator.next());
+ assertTrue(expectedEntriesToIterate.remove(secondEntry));
+
+ assertFalse(iterator.hasNext());
+
+ // Now to the unusual part:
+
+ // does not throw NoSuchElementException
+ Map.Entry<String, String> beyondEnd = copyOf(iterator.next());
+ assertEquals(entryOf(null, null), beyondEnd);
+ iterator.remove(); // removes secondEntry from the mapping!
+ assertEqualsBothWays(
+ Collections.singletonMap(firstEntry.getKey(), firstEntry.getValue()), map);
+
+ // doing it again removes the previous value again
+ iterator.next();
+ iterator.remove();
+ assertEqualsBothWays(Collections.emptyMap(), map);
+
+ // Trying to do this again yields ArrayIndexOutOfBoundsException
+ iterator.next();
+ try {
+ iterator.remove();
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ }
+
+ // But additional calls to next() are tolerated
+ iterator.next();
+ iterator.next();
+ iterator.next();
+ }
+
+ private static<K, V> Map.Entry<K, V> entryOf(K key, V value) {
+ return new AbstractMap.SimpleEntry<>(key, value);
+ }
+
+ private static<K, V> Map.Entry<K, V> copyOf(Map.Entry<K, V> entry) {
+ return entryOf(entry.getKey(), entry.getValue());
+ }
+
+ @Test
+ public void testUnusualBehavior_canIteratePastEnd_keySetIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Set<String> expectedKeysToIterate = new HashSet<>(Arrays.asList("key 1", "key 2"));
+ Iterator<String> iterator = map.keySet().iterator();
+
+ // Assert iteration over the expected two keys in any order
+ assertTrue(iterator.hasNext());
+ String firstKey = iterator.next();
+ assertTrue(expectedKeysToIterate.remove(firstKey));
+
+ assertTrue(iterator.hasNext());
+ String secondKey = iterator.next();
+ assertTrue(expectedKeysToIterate.remove(secondKey));
+
+ assertFalse(iterator.hasNext());
+
+ // Now to the unusual part:
+
+ // does not throw NoSuchElementException
+ String beyondEnd = iterator.next();
+ assertNull(beyondEnd);
+ iterator.remove(); // removes the second entry!
+
+ String firstValue = firstKey.equals("key 1") ? "value 1" : "value 2";
+ assertEqualsBothWays(Collections.singletonMap(firstKey, firstValue), map);
+
+ // doing it again removes the previous value again
+ iterator.next();
+ iterator.remove();
+ assertEqualsBothWays(Collections.emptyMap(), map);
+
+ // Trying to call next() again yields ArrayIndexOutOfBoundsException
+ // This is different from entrySet(), where this was allowed.
+ try {
+ iterator.next();
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ }
+ }
+
+ @Test
+ public void testUnusualBehavior_canIteratePastEnd_valuesIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Set<String> expectedValuesToIterate = new HashSet<>(Arrays.asList("value 1", "value 2"));
+ Iterator<String> iterator = map.values().iterator();
+
+ // Assert iteration over the expected two values in any order
+ assertTrue(iterator.hasNext());
+ String firstValue = iterator.next();
+ assertTrue(expectedValuesToIterate.remove(firstValue));
+
+ assertTrue(iterator.hasNext());
+ String secondValue = iterator.next();
+ assertTrue(expectedValuesToIterate.remove(secondValue));
+
+ assertFalse(iterator.hasNext());
+
+ // Now to the unusual part:
+
+ // does not throw NoSuchElementException
+ String beyondEnd = iterator.next();
+ assertNull(beyondEnd);
+ iterator.remove(); // removes the second entry!
+ String firstKey = firstValue.equals("value 1") ? "key 1" : "key 2";
+ assertEqualsBothWays(Collections.singletonMap(firstKey, firstValue), map);
+
+ // doing it again removes the previous value again
+ iterator.next();
+ iterator.remove();
+ assertEqualsBothWays(Collections.emptyMap(), map);
+
+ // Trying to call next() again yields ArrayIndexOutOfBoundsException
+ // This is different from entrySet(), where this was allowed.
+ try {
+ iterator.next();
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ }
+ }
+
+ /**
+ * The entrySet Iterator returns itself from each call to {@code next()}.
+ * This is unusual behavior for {@link Iterator#next()}; this test ensures that
+ * any future change to this behavior is deliberate.
+ */
+ @Test
+ public void testUnusualBehavior_eachEntryIsSameAsIterator_entrySetIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
+
+ assertSame(iterator, iterator.next());
+ assertSame(iterator, iterator.next());
+ }
+
+ @Test
+ public void testUnusualBehavior_equalsThrowsAfterRemove_entrySetIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
+ iterator.next();
+ iterator.remove();
+ try {
+ iterator.equals(iterator);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ private static<T> void assertEqualsBothWays(T a, T b) {
+ assertEquals(a, b);
+ assertEquals(b, a);
+ assertEquals(a.hashCode(), b.hashCode());
+ }
+
}
diff --git a/tests/tests/util/src/android/util/cts/ArraySetTest.java b/tests/tests/util/src/android/util/cts/ArraySetTest.java
index 554d209..995b651 100644
--- a/tests/tests/util/src/android/util/cts/ArraySetTest.java
+++ b/tests/tests/util/src/android/util/cts/ArraySetTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -513,4 +514,32 @@
Object[] objectArray = arraySet.toArray();
compareArraySetAndRawArray(arraySet, objectArray);
}
+
+ @Test
+ public void testUnusualBehavior_canIteratePastEnd() {
+ ArraySet<String> set = new ArraySet<>();
+ set.add("value");
+ Iterator<String> iterator = set.iterator();
+
+ assertTrue(iterator.hasNext());
+ assertEquals("value", iterator.next());
+ assertFalse(iterator.hasNext());
+
+ // Now to the unusual part:
+
+ // does not throw NoSuchElementException
+ String beyondEnd = iterator.next();
+ assertNull(beyondEnd);
+ iterator.remove(); // removes "value"
+ assertEquals(0, set.size());
+ assertFalse(set.iterator().hasNext());
+
+ // Trying to call next() again yields ArrayIndexOutOfBoundsException
+ // This is different from entrySet(), where this was allowed.
+ try {
+ iterator.next();
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ }
+ }
}
diff --git a/tests/tests/view/src/android/view/cts/AbsSavedStateTest.java b/tests/tests/view/src/android/view/cts/AbsSavedStateTest.java
index ba781cf..12bba10 100644
--- a/tests/tests/view/src/android/view/cts/AbsSavedStateTest.java
+++ b/tests/tests/view/src/android/view/cts/AbsSavedStateTest.java
@@ -56,7 +56,7 @@
@Test
public void testConstructor() {
AbsSavedState superState = new AbsSavedStateImpl(Parcel.obtain());
- assertNull(superState.getSuperState());
+ assertSame(AbsSavedState.EMPTY_STATE, superState.getSuperState());
AbsSavedState s = new AbsSavedStateImpl(superState);
assertSame(superState, s.getSuperState());
@@ -64,22 +64,21 @@
Parcel source = Parcel.obtain();
source.writeParcelable(superState, 0);
source.setDataPosition(0);
- s = new AbsSavedStateImpl(source);
+ s = new AbsSavedStateImpl(source, AbsSavedStateImpl.class.getClassLoader());
assertTrue(s.getSuperState() instanceof AbsSavedState);
source = Parcel.obtain();
s = new AbsSavedStateImpl(source);
assertSame(AbsSavedState.EMPTY_STATE, s.getSuperState());
- ClassLoader loader = AbsSavedState.class.getClassLoader();
source = Parcel.obtain();
source.writeParcelable(superState, 0);
source.setDataPosition(0);
- s = new AbsSavedStateImpl(source, loader);
+ s = new AbsSavedStateImpl(source, AbsSavedStateImpl.class.getClassLoader());
assertTrue(s.getSuperState() instanceof AbsSavedState);
source = Parcel.obtain();
- s = new AbsSavedStateImpl(source, loader);
+ s = new AbsSavedStateImpl(source, AbsSavedState.class.getClassLoader());
assertSame(AbsSavedState.EMPTY_STATE, s.getSuperState());
}
@@ -99,17 +98,20 @@
parcel.setDataPosition(0);
AbsSavedState unparceled = AbsSavedState.CREATOR.createFromParcel(parcel);
assertNotNull(unparceled);
- assertEquals(AbsSavedState.EMPTY_STATE, unparceled.getSuperState());
+ assertNull(unparceled.getSuperState());
AbsSavedState stateWithSuper = new AbsSavedStateImpl(state);
parcel = Parcel.obtain();
stateWithSuper.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
- try {
- AbsSavedState.CREATOR.createFromParcel(parcel);
- fail("Expected IllegalStateException");
- } catch (IllegalStateException e) {
- // Expected.
+ if (AbsSavedState.CREATOR instanceof Parcelable.ClassLoaderCreator) {
+ try {
+ ((Parcelable.ClassLoaderCreator) AbsSavedState.CREATOR).createFromParcel(parcel,
+ AbsSavedStateImpl.class.getClassLoader());
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
}
}
@@ -136,13 +138,19 @@
super(source, loader);
}
- public static final Creator<AbsSavedStateImpl> CREATOR = new Creator<AbsSavedStateImpl>() {
+ public static final Creator<AbsSavedStateImpl> CREATOR =
+ new ClassLoaderCreator<AbsSavedStateImpl>() {
@Override
public AbsSavedStateImpl createFromParcel(Parcel source) {
return new AbsSavedStateImpl(source);
}
@Override
+ public AbsSavedStateImpl createFromParcel(Parcel source, ClassLoader loader) {
+ return new AbsSavedStateImpl(source, loader);
+ }
+
+ @Override
public AbsSavedStateImpl[] newArray(int size) {
return new AbsSavedStateImpl[size];
}
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index 5421574..5272f3c 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -295,6 +295,15 @@
android:layout_height="wrap_content"
android:fontFamily="@font/samplexmlfont" />
+ <!-- This is here to test that the TextView constructor ignores references to
+ non Font resource types in the fontFamily attribute.-->
+ <TextView
+ android:id="@+id/textview_fontxmlresource_nonFontReference"
+ android:text="@string/text_view_hello"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@style/TextView_FontResource" />
+
<TextView
android:id="@+id/textview_fontresource_style"
android:text="@string/text_view_hello"
diff --git a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
index 9b03467..68aaa08 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -1261,10 +1261,19 @@
int measuredWidth = popupRoot.getMeasuredWidth();
int measuredHeight = popupRoot.getMeasuredHeight();
View anchor = mActivity.findViewById(R.id.anchor_middle);
+
+ // The popup should occupy all available vertical space.
int[] anchorLocationInWindowXY = new int[2];
anchor.getLocationInWindow(anchorLocationInWindowXY);
+ assertEquals(measuredHeight,
+ parentHeight - (anchorLocationInWindowXY[1] + anchor.getHeight()));
- assertEquals(measuredHeight, parentHeight - anchorLocationInWindowXY[1]);
+ // The popup should be vertically aligned to the anchor's bottom edge.
+ int[] anchorLocationOnScreenXY = new int[2];
+ anchor.getLocationOnScreen(anchorLocationOnScreenXY);
+ int[] popupLocationOnScreenXY = new int[2];
+ popupRoot.getLocationOnScreen(popupLocationOnScreenXY);
+ assertEquals(anchorLocationOnScreenXY[1] + anchor.getHeight(), popupLocationOnScreenXY[1]);
}
@Test