merge in mnc-dr-release history after reset to mnc-dr-dev
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index f4cd7d7..8e0d83d 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -19,6 +19,8 @@
CtsDocumentClient \
CtsExternalStorageApp \
CtsInstrumentationAppDiffCert \
+ CtsUsePermissionApp \
+ CtsUsePermissionAppCompat \
CtsPermissionDeclareApp \
CtsPermissionDeclareAppCompat \
CtsReadExternalStorageApp \
@@ -97,6 +99,7 @@
CtsUsbSerialTestApp \
CtsVoiceInteractionService \
CtsVoiceInteractionApp \
+ CtsVoiceSettingsService \
$(cts_security_apps_list) \
$(cts_security_keysets_list)
@@ -125,6 +128,7 @@
CtsAccessibilityServiceTestCases \
CtsAccessibilityTestCases \
CtsAdminTestCases \
+ CtsAlarmClockTestCases \
CtsAnimationTestCases \
CtsAppTestCases \
CtsAppWidgetTestCases \
@@ -186,6 +190,7 @@
CtsUtilTestCases \
CtsViewTestCases \
CtsVoiceInteractionTestCases \
+ CtsVoiceSettingsTestCases \
CtsWebkitTestCases \
CtsWidgetTestCases
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index b6d398f..95f19d9 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -307,6 +307,35 @@
return props.has_key("android.request.availableCapabilities") and \
4 in props["android.request.availableCapabilities"]
+def noise_reduction_mode(props, mode):
+ """Returns whether a device supports the noise reduction mode.
+
+ Args:
+ props: Camera properties objects.
+ mode: Integer, indicating the noise reduction mode to check for
+ availability.
+
+ Returns:
+ Boolean.
+ """
+ return props.has_key(
+ "android.noiseReduction.availableNoiseReductionModes") and mode \
+ in props["android.noiseReduction.availableNoiseReductionModes"];
+
+def edge_mode(props, mode):
+ """Returns whether a device supports the edge mode.
+
+ Args:
+ props: Camera properties objects.
+ mode: Integer, indicating the edge mode to check for availability.
+
+ Returns:
+ Boolean.
+ """
+ return props.has_key(
+ "android.edge.availableEdgeModes") and mode \
+ in props["android.edge.availableEdgeModes"];
+
class __UnitTest(unittest.TestCase):
"""Run a suite of unit tests on this module.
"""
diff --git a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
index f5176a7..27da8fc 100644
--- a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
@@ -45,7 +45,8 @@
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
its.caps.skip_unless(its.caps.compute_target_exposure(props) and
- its.caps.per_frame_control(props))
+ its.caps.per_frame_control(props) and
+ its.caps.noise_reduction_mode(props, 0))
# NR mode 0 with low gain
e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
@@ -62,37 +63,51 @@
ref_variance.append(its.image.compute_image_variances(tile)[0])
print "Ref variances:", ref_variance
- for i in range(3):
- # NR modes 0, 1, 2 with high gain
+ # NR modes 0, 1, 2, 3, 4 with high gain
+ for mode in range(5):
+ # Skip unavailable modes
+ if not its.caps.noise_reduction_mode(props, mode):
+ nr_modes_reported.append(mode)
+ for channel in range(3):
+ variances[channel].append(0)
+ continue;
+
e, s = its.target.get_target_exposure_combos(cam)["maxSensitivity"]
req = its.objects.manual_capture_request(s, e)
- req["android.noiseReduction.mode"] = i
+ req["android.noiseReduction.mode"] = mode
cap = cam.do_capture(req)
nr_modes_reported.append(
cap["metadata"]["android.noiseReduction.mode"])
its.image.write_image(
its.image.convert_capture_to_rgb_image(cap),
- "%s_high_gain_nr=%d.jpg" % (NAME, i))
+ "%s_high_gain_nr=%d.jpg" % (NAME, mode))
planes = its.image.convert_capture_to_planes(cap)
for j in range(3):
img = planes[j]
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
variance = its.image.compute_image_variances(tile)[0]
variances[j].append(variance / ref_variance[j])
- print "Variances with NR mode [0,1,2]:", variances
+ print "Variances with NR mode [0,1,2,3,4]:", variances
# Draw a plot.
for j in range(3):
- pylab.plot(range(3), variances[j], "rgb"[j])
+ pylab.plot(range(5), variances[j], "rgb"[j])
matplotlib.pyplot.savefig("%s_plot_variances.png" % (NAME))
- assert(nr_modes_reported == [0,1,2])
+ assert(nr_modes_reported == [0,1,2,3,4])
# Check that the variance of the NR=0 image is higher than for the
# NR=1 and NR=2 images.
for j in range(3):
- for i in range(1,3):
- assert(variances[j][i] < variances[j][0])
+ for mode in [1,2]:
+ if its.caps.noise_reduction_mode(props, mode):
+ assert(variances[j][mode] < variances[j][0])
+ # Variance of MINIMAL should be higher than for FAST, HQ
+ if its.caps.noise_reduction_mode(props, 3):
+ assert(variances[j][mode] < variances[j][3])
+ # Variance of ZSL should be higher than for FAST, HQ
+ if its.caps.noise_reduction_mode(props, 4):
+ assert(variances[j][mode] < variances[j][4])
if __name__ == '__main__':
main()
diff --git a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
index e9240ba..c2657c7 100644
--- a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
@@ -42,9 +42,13 @@
its.caps.skip_unless(its.caps.compute_target_exposure(props) and
its.caps.per_frame_control(props) and
+ its.caps.noise_reduction_mode(props, 0) and
(its.caps.yuv_reprocess(props) or
its.caps.private_reprocess(props)))
+ # If reprocessing is supported, ZSL NR mode must be avaiable.
+ assert(its.caps.noise_reduction_mode(props, 4))
+
reprocess_formats = []
if (its.caps.yuv_reprocess(props)):
reprocess_formats.append("yuv")
@@ -73,8 +77,14 @@
ref_variance = its.image.compute_image_variances(tile)
print "Ref variances:", ref_variance
- for nr_mode in range(3):
- # NR modes 0, 1, 2 with high gain
+ for nr_mode in range(5):
+ # Skipp unavailable modes
+ if not its.caps.noise_reduction_mode(props, nr_mode):
+ nr_modes_reported.append(nr_mode)
+ variances.append(0)
+ continue
+
+ # NR modes with high gain
e, s = its.target.get_target_exposure_combos(cam) \
["maxSensitivity"]
req = its.objects.manual_capture_request(s, e)
@@ -91,21 +101,31 @@
variance = its.image.compute_image_variances(tile)
variances.append(
[variance[chan] / ref_variance[chan] for chan in range(3)])
- print "Variances with NR mode [0,1,2]:", variances
+ print "Variances with NR mode [0,1,2,3,4]:", variances
# Draw a plot.
- for nr_mode in range(3):
- pylab.plot(range(3), variances[nr_mode], "rgb"[nr_mode])
+ for nr_mode in range(5):
+ if not its.caps.noise_reduction_mode(props, nr_mode):
+ continue
+ pylab.plot(range(3), variances[nr_mode], "rgbcm"[nr_mode])
matplotlib.pyplot.savefig("%s_plot_%s_variances.png" %
(NAME, reprocess_format))
- assert(nr_modes_reported == [0,1,2])
+ assert(nr_modes_reported == [0,1,2,3,4]
- # Check that the variance of the NR=0 image is higher than for the
- # NR=1 and NR=2 images.
- for j in range(3):
- for i in range(1,3):
- assert(variances[i][j] < variances[0][j])
+ # Check that the variances of the NR=0 and NR=3 and NR=4 images are
+ # higher than for the NR=1 and NR=2 images.
+ for channel in range(3):
+ for nr_mode in [1, 2]:
+ if its.caps.noise_reduction_mode(props, nr_mode):
+ assert(variances[nr_mode][channel] <
+ variances[0][channel])
+ if its.caps.noise_reduction_mode(props, 3):
+ assert(variances[nr_mode][channel] <
+ variances[3][channel])
+ if its.caps.noise_reduction_mode(props, 4):
+ assert(variances[nr_mode][channel] <
+ variances[4][channel])
if __name__ == '__main__':
main()
diff --git a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
index 1966d9e..77208e7 100644
--- a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
@@ -90,9 +90,13 @@
its.caps.skip_unless(its.caps.read_3a(props) and
its.caps.per_frame_control(props) and
+ its.caps.edge_mode(props, 0) and
(its.caps.yuv_reprocess(props) or
its.caps.private_reprocess(props)))
+ # If reprocessing is supported, ZSL EE mode must be avaiable.
+ assert(its.caps.edge_mode(props, 3))
+
reprocess_formats = []
if (its.caps.yuv_reprocess(props)):
reprocess_formats.append("yuv")
@@ -108,13 +112,18 @@
# Get the sharpness for each edge mode for regular requests
sharpness_regular = []
edge_mode_reported_regular = []
- for edge_mode in range(3):
+ for edge_mode in range(4):
+ # Skip unavailable modes
+ if not its.caps.edge_mode(props, edge_mode):
+ edge_mode_reported_regular.append(edge_mode)
+ sharpness_regular.append(0)
+ continue
ret = test_edge_mode(cam, edge_mode, s, e, fd, out_surface)
edge_mode_reported_regular.append(ret["edge_mode"])
sharpness_regular.append(ret["sharpness"])
print "Reported edge modes:", edge_mode_reported_regular
- print "Sharpness with EE mode [0,1,2]:", sharpness_regular
+ print "Sharpness with EE mode [0,1,2,3]:", sharpness_regular
# Get the sharpness for each reprocess format and edge mode for
# reprocess requests.
@@ -125,7 +134,13 @@
# List of sharpness
sharpnesses = []
edge_mode_reported = []
- for edge_mode in range(3):
+ for edge_mode in range(4):
+ # Skip unavailable modes
+ if not its.caps.edge_mode(props, edge_mode):
+ edge_mode_reported.append(edge_mode)
+ sharpnesses.append(0)
+ continue
+
ret = test_edge_mode(cam, edge_mode, s, e, fd, out_surface,
reprocess_format)
edge_mode_reported.append(ret["edge_mode"])
@@ -135,24 +150,31 @@
edge_mode_reported_reprocess.append(edge_mode_reported)
print "Reported edge modes:", edge_mode_reported
- print "Sharpness with EE mode [0,1,2] for %s reprocess:" % \
+ print "Sharpness with EE mode [0,1,2,3] for %s reprocess:" % \
(reprocess_format) , sharpnesses
- # Verify the results
- assert(edge_mode_reported_regular == [0,1,2])
- assert(sharpness_regular[1] >
- sharpness_regular[0] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
- assert(sharpness_regular[2] >
- sharpness_regular[0] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+ # Verify reported modes for regular results
+ assert(edge_mode_reported_regular == [0,1,2,3])
+ # Verify the images for all modes are not too blurrier than OFF.
+ for edge_mode in [1, 2, 3]:
+ if its.caps.edge_mode(props, edge_mode):
+ assert(sharpness_regular[edge_mode] >
+ sharpness_regular[0] *
+ (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+ # Verify the image for ZSL are not too sharper than OFF
+ assert(sharpness_regular[3] <
+ sharpness_regular[0] * (1.0 + THRESHOLD_RELATIVE_SHARPNESS_DIFF))
- # Verify the reprocess
+ # Verify sharpness of reprocess captures are similar to sharpness of
+ # regular captures.
for reprocess_format in range(len(reprocess_formats)):
- assert(edge_mode_reported_reprocess[reprocess_format] == [0,1,2])
- for edge_mode in range(3):
- assert(sharpnesses_reprocess[reprocess_format][edge_mode] >=
- (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF) *
- sharpnesses_reprocess[reprocess_format][0] *
- sharpness_regular[edge_mode] / sharpness_regular[0])
+ assert(edge_mode_reported_reprocess[reprocess_format] == [0,1,2,3])
+ for edge_mode in range(4):
+ if its.caps.edge_mode(props, edge_mode):
+ assert(sharpnesses_reprocess[reprocess_format][edge_mode] >=
+ (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF) *
+ sharpnesses_reprocess[reprocess_format][0] *
+ sharpness_regular[edge_mode] / sharpness_regular[0])
if __name__ == '__main__':
main()
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java
index b4a6416..5a60ad0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java
@@ -42,7 +42,7 @@
public class BleAdvertiserService extends Service {
public static final boolean DEBUG = true;
- public static final String TAG = "BleAdvertiseService";
+ public static final String TAG = "BleAdvertiserService";
public static final int COMMAND_START_ADVERTISE = 0;
public static final int COMMAND_STOP_ADVERTISE = 1;
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java
new file mode 100644
index 0000000..091da24
--- /dev/null
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appsecurity;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+public class PermissionsHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+ private static final String PKG = "com.android.cts.usepermission";
+
+ private static final String APK = "CtsUsePermissionApp.apk";
+ private static final String APK_COMPAT = "CtsUsePermissionAppCompat.apk";
+
+ private IAbi mAbi;
+ private CtsBuildHelper mCtsBuild;
+
+ @Override
+ public void setAbi(IAbi abi) {
+ mAbi = abi;
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ assertNotNull(mAbi);
+ assertNotNull(mCtsBuild);
+
+ getDevice().uninstallPackage(PKG);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ getDevice().uninstallPackage(PKG);
+ }
+
+ public void testFail() throws Exception {
+ // Sanity check that remote failure is host failure
+ assertNull(getDevice().installPackage(mCtsBuild.getTestApp(APK), false, false));
+ try {
+ runDeviceTests(PKG, ".UsePermissionTest", "testFail");
+ fail("Expected remote failure");
+ } catch (AssertionError expected) {
+ }
+ }
+
+ public void testKill() throws Exception {
+ // Sanity check that remote kill is host failure
+ assertNull(getDevice().installPackage(mCtsBuild.getTestApp(APK), false, false));
+ try {
+ runDeviceTests(PKG, ".UsePermissionTest", "testKill");
+ fail("Expected remote failure");
+ } catch (AssertionError expected) {
+ }
+ }
+
+ public void testDefault() throws Exception {
+ assertNull(getDevice().installPackage(mCtsBuild.getTestApp(APK), false, false));
+ runDeviceTests(PKG, ".UsePermissionTest", "testDefault");
+ }
+
+ public void testGranted() throws Exception {
+ assertNull(getDevice().installPackage(mCtsBuild.getTestApp(APK), false, false));
+ grantPermission(PKG, "android.permission.WRITE_EXTERNAL_STORAGE");
+ runDeviceTests(PKG, ".UsePermissionTest", "testGranted");
+ }
+
+ public void testInteractiveGrant() throws Exception {
+ assertNull(getDevice().installPackage(mCtsBuild.getTestApp(APK), false, false));
+ runDeviceTests(PKG, ".UsePermissionTest", "testInteractiveGrant");
+ }
+
+ public void testCompatDefault() throws Exception {
+ assertNull(getDevice().installPackage(mCtsBuild.getTestApp(APK_COMPAT), false, false));
+ runDeviceTests(PKG, ".UsePermissionCompatTest", "testCompatDefault");
+ }
+
+ public void testCompatRevoked() throws Exception {
+ assertNull(getDevice().installPackage(mCtsBuild.getTestApp(APK_COMPAT), false, false));
+ setAppOps(PKG, "android:read_external_storage", "deny");
+ setAppOps(PKG, "android:write_external_storage", "deny");
+ runDeviceTests(PKG, ".UsePermissionCompatTest", "testCompatRevoked");
+ }
+
+ private void runDeviceTests(String packageName, String testClassName, String testMethodName)
+ throws DeviceNotAvailableException {
+ Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+ }
+
+ private void grantPermission(String pkg, String permission) throws Exception {
+ assertEmpty(getDevice().executeShellCommand("pm grant " + pkg + " " + permission));
+ }
+
+ private void revokePermission(String pkg, String permission) throws Exception {
+ assertEmpty(getDevice().executeShellCommand("pm revoke " + pkg + " " + permission));
+ }
+
+ private void setAppOps(String pkg, String op, String mode) throws Exception {
+ assertEmpty(getDevice().executeShellCommand("appops set " + pkg + " " + op + " " + mode));
+ }
+
+ private static void assertEmpty(String str) {
+ if (str == null || str.length() == 0) {
+ return;
+ } else {
+ fail("Expected empty string but found " + str);
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
index aa09f75..379de5f 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
@@ -351,7 +351,7 @@
}
}
- private static void logCommand(String... cmd) throws Exception {
+ public static void logCommand(String... cmd) throws Exception {
final Process proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
final ByteArrayOutputStream buf = new ByteArrayOutputStream();
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp/Android.mk
new file mode 100644
index 0000000..f91d0c4
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp/Android.mk
@@ -0,0 +1,35 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ctsdeviceutil ctstestrunner ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
+
+LOCAL_PACKAGE_NAME := CtsUsePermissionApp
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp/AndroidManifest.xml
new file mode 100644
index 0000000..253d85d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.usepermission">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".MyActivity" />
+ </application>
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.usepermission" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/MyActivity.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/MyActivity.java
new file mode 100644
index 0000000..5af3886
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/MyActivity.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.usepermission;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+
+public class MyActivity extends Activity {
+ private final SynchronousQueue<Result> mResult = new SynchronousQueue<>();
+
+ public static class Result {
+ public final int requestCode;
+ public final String[] permissions;
+ public final int[] grantResults;
+
+ public Result(int requestCode, String[] permissions, int[] grantResults) {
+ this.requestCode = requestCode;
+ this.permissions = permissions;
+ this.grantResults = grantResults;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ try {
+ mResult.offer(new Result(requestCode, permissions, grantResults), 5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Result getResult() {
+ try {
+ return mResult.take();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/UsePermissionTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/UsePermissionTest.java
new file mode 100644
index 0000000..d464e48
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/UsePermissionTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.usepermission;
+
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.logCommand;
+
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiSelector;
+import android.test.InstrumentationTestCase;
+
+public class UsePermissionTest extends InstrumentationTestCase {
+ private static final String TAG = "UsePermissionTest";
+
+ private UiDevice mDevice;
+ private MyActivity mActivity;
+
+ public void testFail() throws Exception {
+ fail("Expected");
+ }
+
+ public void testKill() throws Exception {
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+
+ public void testDefault() throws Exception {
+ logCommand("/system/bin/cat", "/proc/self/mountinfo");
+
+ // New permission model is denied by default
+ assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
+ .checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE));
+ assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+ assertDirNoAccess(Environment.getExternalStorageDirectory());
+ assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+ }
+
+ public void testGranted() throws Exception {
+ logCommand("/system/bin/cat", "/proc/self/mountinfo");
+
+ assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+ .checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE));
+ assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+ assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
+ assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+ }
+
+ public void testInteractiveGrant() throws Exception {
+ logCommand("/system/bin/cat", "/proc/self/mountinfo");
+
+ // Start out without permission
+ assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
+ .checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE));
+ assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+ assertDirNoAccess(Environment.getExternalStorageDirectory());
+ assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+
+ // Go through normal grant flow
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
+ MyActivity.class, null);
+ mDevice.waitForIdle();
+
+ mActivity.requestPermissions(new String[] {
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE }, 42);
+ mDevice.waitForIdle();
+
+ new UiObject(new UiSelector()
+ .resourceId("com.android.packageinstaller:id/permission_allow_button")).click();
+ mDevice.waitForIdle();
+
+ final MyActivity.Result result = mActivity.getResult();
+ assertEquals(42, result.requestCode);
+ assertEquals(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, result.permissions[0]);
+ assertEquals(PackageManager.PERMISSION_GRANTED, result.grantResults[0]);
+
+ logCommand("/system/bin/cat", "/proc/self/mountinfo");
+
+ // We should have permission now!
+ assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+ .checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE));
+ assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+ assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
+ assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+
+ mActivity.finish();
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/Android.mk
new file mode 100644
index 0000000..70b4b0f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/Android.mk
@@ -0,0 +1,35 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := 21
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ctsdeviceutil ctstestrunner ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
+
+LOCAL_PACKAGE_NAME := CtsUsePermissionAppCompat
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/AndroidManifest.xml
new file mode 100644
index 0000000..253d85d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.usepermission">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".MyActivity" />
+ </application>
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.usepermission" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/src/com/android/cts/usepermission/UsePermissionCompatTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/src/com/android/cts/usepermission/UsePermissionCompatTest.java
new file mode 100644
index 0000000..b30e8a5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/src/com/android/cts/usepermission/UsePermissionCompatTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.usepermission;
+
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.logCommand;
+
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.os.Process;
+import android.test.InstrumentationTestCase;
+
+import com.android.cts.externalstorageapp.CommonExternalStorageTest;
+
+public class UsePermissionCompatTest extends InstrumentationTestCase {
+ private static final String TAG = "UsePermissionTest";
+
+ public void testCompatDefault() throws Exception {
+ logCommand("/system/bin/cat", "/proc/self/mountinfo");
+
+ // Legacy permission model is granted by default
+ assertEquals(PackageManager.PERMISSION_GRANTED,
+ getInstrumentation().getContext().checkPermission(
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE, Process.myPid(),
+ Process.myUid()));
+ assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+ assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
+ assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+ }
+
+ public void testCompatRevoked() throws Exception {
+ CommonExternalStorageTest.logCommand("/system/bin/cat", "/proc/self/mountinfo");
+
+ // Legacy permission model appears granted, but storage looks and
+ // behaves like it's ejected
+ assertEquals(PackageManager.PERMISSION_GRANTED,
+ getInstrumentation().getContext().checkPermission(
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE, Process.myPid(),
+ Process.myUid()));
+ assertEquals(Environment.MEDIA_UNMOUNTED, Environment.getExternalStorageState());
+ assertDirNoAccess(Environment.getExternalStorageDirectory());
+ assertNull(getInstrumentation().getContext().getExternalCacheDir());
+ }
+}
diff --git a/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java b/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java
index 286d4fd..f9daa3c 100644
--- a/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java
+++ b/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java
@@ -80,7 +80,7 @@
@TimeoutReq(minutes = 8)
public void testChannels() throws Exception {
if (!mHasTvInputFramework) return;
- double[] averages = new double[4];
+ double[] averages = new double[5];
// Insert
final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
@@ -138,23 +138,42 @@
applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
averages[1] = Stat.getAverage(applyBatchTimes);
- // Query
+ // Query channels
applyBatchTimes = MeasureTime.measure(QUERY_RUNS, new MeasureRun() {
@Override
public void run(int i) {
- int j = 0;
try (Cursor cursor = mContentResolver.query(Channels.CONTENT_URI, null, null,
null, null)) {
while (cursor.moveToNext()) {
- ++j;
+ // Do nothing. Just iterate all the items.
}
}
}
});
- getReportLog().printArray("Elapsed time for query: ",
+ getReportLog().printArray("Elapsed time for query (channels): ",
applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
averages[2] = Stat.getAverage(applyBatchTimes);
+ // Query a channel
+ try (final Cursor cursor = mContentResolver.query(Channels.CONTENT_URI,
+ projection, null, null, null)) {
+ final Uri channelUri = TvContract.buildChannelUri(cursor.getLong(0));
+ applyBatchTimes = MeasureTime.measure(QUERY_RUNS, new MeasureRun() {
+ @Override
+ public void run(int i) {
+ assertTrue(cursor.moveToNext());
+ try (Cursor c = mContentResolver.query(channelUri, null, null, null, null)) {
+ while (c.moveToNext()) {
+ // Do nothing. Just iterate all the items.
+ }
+ }
+ }
+ });
+ }
+ getReportLog().printArray("Elapsed time for query (a channel): ",
+ applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+ averages[3] = Stat.getAverage(applyBatchTimes);
+
// Delete
applyBatchTimes = MeasureTime.measure(1, new MeasureRun() {
@Override
@@ -164,16 +183,17 @@
});
getReportLog().printArray("Elapsed time for delete: ",
applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
- averages[3] = Stat.getAverage(applyBatchTimes);
+ averages[4] = Stat.getAverage(applyBatchTimes);
- getReportLog().printArray("Average elapsed time for (insert, update, query, delete): ",
+ getReportLog().printArray("Average elapsed time for insert, update, query (channels), "
+ + "query (a channel), delete: ",
averages, ResultType.LOWER_BETTER, ResultUnit.MS);
}
@TimeoutReq(minutes = 12)
public void testPrograms() throws Exception {
if (!mHasTvInputFramework) return;
- double[] averages = new double[6];
+ double[] averages = new double[7];
// Prepare (insert channels)
final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
@@ -262,20 +282,19 @@
applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
averages[1] = Stat.getAverage(applyBatchTimes);
- // Query
+ // Query programs
applyBatchTimes = MeasureTime.measure(QUERY_RUNS, new MeasureRun() {
@Override
public void run(int i) {
- int j = 0;
try (Cursor cursor = mContentResolver.query(Programs.CONTENT_URI, null, null,
null, null)) {
while (cursor.moveToNext()) {
- ++j;
+ // Do nothing. Just iterate all the items.
}
}
}
});
- getReportLog().printArray("Elapsed time for query: ",
+ getReportLog().printArray("Elapsed time for query (programs): ",
applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
averages[2] = Stat.getAverage(applyBatchTimes);
@@ -284,22 +303,41 @@
@Override
public void run(int i) {
Uri channelUri = channelUris.get(i);
- int j = 0;
try (Cursor cursor = mContentResolver.query(
TvContract.buildProgramsUriForChannel(
channelUri, 0,
PROGRAM_DURATION_MS * TRANSACTION_SIZE / 2),
null, null, null, null)) {
while (cursor.moveToNext()) {
- ++j;
+ // Do nothing. Just iterate all the items.
}
}
}
});
- getReportLog().printArray("Elapsed time for query with selection: ",
+ getReportLog().printArray("Elapsed time for query (programs with selection): ",
applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
averages[3] = Stat.getAverage(applyBatchTimes);
+ // Query a program
+ try (final Cursor cursor = mContentResolver.query(Programs.CONTENT_URI,
+ projection, null, null, null)) {
+ final Uri programUri = TvContract.buildProgramUri(cursor.getLong(0));
+ applyBatchTimes = MeasureTime.measure(QUERY_RUNS, new MeasureRun() {
+ @Override
+ public void run(int i) {
+ assertTrue(cursor.moveToNext());
+ try (Cursor c = mContentResolver.query(programUri, null, null, null, null)) {
+ while (c.moveToNext()) {
+ // Do nothing. Just iterate all the items.
+ }
+ }
+ }
+ });
+ }
+ getReportLog().printArray("Elapsed time for query (a program): ",
+ applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+ averages[4] = Stat.getAverage(applyBatchTimes);
+
// Delete programs
applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
@Override
@@ -315,7 +353,7 @@
});
getReportLog().printArray("Elapsed time for delete programs: ",
applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
- averages[4] = Stat.getAverage(applyBatchTimes);
+ averages[5] = Stat.getAverage(applyBatchTimes);
// Delete channels
applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
@@ -327,10 +365,11 @@
});
getReportLog().printArray("Elapsed time for delete channels: ",
applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
- averages[5] = Stat.getAverage(applyBatchTimes);
+ averages[6] = Stat.getAverage(applyBatchTimes);
- getReportLog().printArray("Average elapsed time for (insert, update, query, "
- + "query with selection, delete channels, delete programs): ",
+ getReportLog().printArray("Average elapsed time for insert, update, query (programs), "
+ + "query (programs with selection), query (a channel), delete (channels), "
+ + "delete (programs): ",
averages, ResultType.LOWER_BETTER, ResultUnit.MS);
}
}
diff --git a/tests/tests/alarmclock/Android.mk b/tests/tests/alarmclock/Android.mk
new file mode 100644
index 0000000..c99f953
--- /dev/null
+++ b/tests/tests/alarmclock/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctsdeviceutil
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAlarmClockTestCases
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/alarmclock/AndroidManifest.xml b/tests/tests/alarmclock/AndroidManifest.xml
new file mode 100644
index 0000000..88bb91d
--- /dev/null
+++ b/tests/tests/alarmclock/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.alarmclock.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="TestActivity"
+ android:label="The Target Activity for alarmClock Intents Test">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.alarmclock.cts"
+ android:label="CTS tests of android.alarmclock">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockIntentsTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockIntentsTest.java
new file mode 100644
index 0000000..33ac546
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockIntentsTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmclock.cts;
+
+import android.app.AlarmManager;
+import android.app.AlarmManager.AlarmClockInfo;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.AlarmClock;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.util.Calendar;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class AlarmClockIntentsTest extends ActivityInstrumentationTestCase2<TestActivity> {
+ static final String TAG = "AlarmClockIntentsTest";
+ static final int ALARM_TEST_WINDOW_MINS = 2;
+ private static final int TIMEOUT_MS = 20 * 1000;
+
+ private TestActivity mActivity;
+ private AlarmManager mAlarmManager;
+ private NextAlarmReceiver mReceiver = new NextAlarmReceiver();
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private Context mContext;
+
+ public AlarmClockIntentsTest() {
+ super(TestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getTargetContext();
+ mActivity = getActivity();
+ mAlarmManager = (AlarmManager) mActivity.getSystemService(Context.ALARM_SERVICE);
+ IntentFilter filter = new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ filter.addAction(AlarmClock.ACTION_SET_ALARM);
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mContext.unregisterReceiver(mReceiver);
+ super.tearDown();
+ }
+
+ public void testSetAlarm() throws Exception {
+ // set an alarm for ALARM_TEST_WINDOW millisec from now.
+ // Assume the next alarm is NOT within the next ALARM_TEST_WINDOW millisec
+ // TODO: fix this assumption
+ AlarmTime expected = getAlarmTime();
+
+ // set the alarm
+ assertNotNull(mActivity);
+ assertTrue(mActivity.setAlarm(expected));
+
+ // wait for the alarm to be set
+ if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Failed to receive broadcast in " + (TIMEOUT_MS / 1000) + "sec");
+ }
+
+ // verify the next alarm
+ assertTrue(isNextAlarmSameAs(expected));
+ }
+
+ public void testDismissAlarm() throws Exception {
+ assertTrue(mActivity.cancelAlarm(getAlarmTime()));
+ }
+
+ public void testSnoozeAlarm() throws Exception {
+ assertTrue(mActivity.snoozeAlarm());
+ }
+
+ private AlarmTime getAlarmTime() {
+ Calendar now = Calendar.getInstance();
+ now.add(Calendar.MINUTE, ALARM_TEST_WINDOW_MINS);
+ long nextAlarmTime = now.getTimeInMillis();
+ return new AlarmTime(nextAlarmTime);
+ }
+
+ private boolean isNextAlarmSameAs(AlarmTime expected) {
+ AlarmClockInfo alarmInfo = mAlarmManager.getNextAlarmClock();
+ if (alarmInfo == null) {
+ return false;
+ }
+ AlarmTime next = new AlarmTime(alarmInfo.getTriggerTime());
+ if (expected.mIsPm != next.mIsPm ||
+ expected.mHour != next.mHour ||
+ expected.mMinute != next.mMinute) {
+ Log.i(TAG, "Next Alarm time is not same expected time: " +
+ "next = " + next + ", expected = " + expected);
+ return false;
+ }
+ return true;
+ }
+
+ class NextAlarmReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
+ Log.i(TAG, "received broadcast");
+ mLatch.countDown();
+ }
+ }
+ }
+
+ static class AlarmTime {
+ int mHour;
+ int mMinute;
+ boolean mIsPm;
+
+ AlarmTime(long l) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(l);
+ mHour = cal.get(Calendar.HOUR);
+ mMinute = cal.get(Calendar.MINUTE);
+ mIsPm = cal.get(Calendar.AM_PM) == 1;
+ Log.i(TAG, "Calendar converted is: " + cal);
+ }
+
+ AlarmTime(int hour, int minute, boolean isPM) {
+ mHour = hour;
+ mMinute = minute;
+ mIsPm = isPM;
+ }
+
+ @Override
+ public String toString() {
+ String isPmString = (mIsPm) ? "pm" : "am";
+ return mHour + ":" + mMinute + isPmString;
+ }
+ }
+}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/TestActivity.java b/tests/tests/alarmclock/src/android/alarmclock/cts/TestActivity.java
new file mode 100644
index 0000000..9ec33ae
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/TestActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmclock.cts;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.AlarmClock;
+import android.util.Log;
+
+import java.util.Calendar;
+
+public class TestActivity extends Activity {
+ static final String TAG = "TestActivity";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, " in onCreate");
+ }
+
+ private void setParams(int hour, int minute, boolean isPM, Intent intent) {
+ intent.putExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE, AlarmClock.ALARM_SEARCH_MODE_TIME);
+ intent.putExtra(AlarmClock.EXTRA_IS_PM, isPM);
+ intent.putExtra(AlarmClock.EXTRA_HOUR, hour);
+ intent.putExtra(AlarmClock.EXTRA_MINUTES, minute);
+ }
+
+ private boolean start(Intent intent) {
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.wtf(TAG, e);
+ return false;
+ }
+ return true;
+ }
+
+ public boolean cancelAlarm(AlarmClockIntentsTest.AlarmTime time) {
+ Intent intent = new Intent(AlarmClock.ACTION_DISMISS_ALARM);
+ setParams(time.mHour, time.mMinute, time.mIsPm, intent);
+ Log.i(TAG, "sending DISMISS_ALARM intent for: " + time + ", Intent = " + intent);
+ return start(intent);
+ }
+
+ public boolean setAlarm(AlarmClockIntentsTest.AlarmTime time) {
+ Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM);
+ int hour = time.mHour;
+ if (time.mIsPm) {
+ hour += 12;
+ }
+ setParams(hour, time.mMinute, time.mIsPm, intent);
+ Log.i(TAG, "Setting alarm: " + hour + ":" + time.mMinute + ", Intent = " + intent);
+ return start(intent);
+ }
+
+ public boolean snoozeAlarm() {
+ Intent intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
+ Log.i(TAG, "sending SNOOZE_ALARM intent." + intent);
+ return start(intent);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java b/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java
index 42b8d33..10992c5 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java
@@ -32,15 +32,12 @@
protected static final String LOG_TAG = "TestRunner";
/**
- * By default tests need to run in a {@link TestSensorEnvironment} that assumes each sensor is
- * running with a load of several listeners, requesting data at different rates.
- *
- * In a better world the component acting as builder of {@link SensorOperation} would compute
- * this value based on the tests composed.
- *
- * Ideally, each {@link Sensor} object would expose this information to clients.
+ * Previously for L release, we had this flag to know if each sensor is running with multiple
+ * listeners each requesting different data rates. Now before running CTS tests all sensors
+ * are de-activated by putting SensorService in RESTRICTED mode. Only CTS tests can
+ * activate/deactivate sensors in this mode. So we can default this flag value to false.
*/
- private volatile boolean mEmulateSensorUnderLoad = true;
+ private volatile boolean mEmulateSensorUnderLoad = false;
/**
* By default the test class is the root of the test hierarchy.
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java
index 1e775e3..4c449dd 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java
@@ -82,4 +82,8 @@
this.previousEvent = previousEvent;
}
}
+
+ protected double nanosToMillis(long nanos) {
+ return nanos/(1000.0 * 1000.0);
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerification.java
index b692f0f..b0c0fc0 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerification.java
@@ -28,6 +28,10 @@
// Number of events to truncate (discard) from the initial events received
private static final int TRUNCATE_EVENTS_COUNT = 1;
+ // Number of event gaps to tolerate is the minimum of 1% of total events received or 10.
+ private static final int EVENT_GAP_TOLERANCE_COUNT = 10;
+ private static final double EVENT_GAP_TOLERANCE_PERCENT = 0.01;
+
private final int mExpectedDelayUs;
private final List<IndexedEventPair> mEventGaps = new LinkedList<IndexedEventPair>();
@@ -68,24 +72,24 @@
}
final int count = mEventGaps.size();
- stats.addValue(PASSED_KEY, count == 0);
+ int eventGapTolerance = (int) Math.min(EVENT_GAP_TOLERANCE_COUNT, 0.01 * mIndex);
+ stats.addValue(PASSED_KEY, count <= eventGapTolerance);
stats.addValue(SensorStats.EVENT_GAP_COUNT_KEY, count);
stats.addValue(SensorStats.EVENT_GAP_POSITIONS_KEY, getIndexArray(mEventGaps));
- if (count > 0) {
+ if (count > eventGapTolerance) {
StringBuilder sb = new StringBuilder();
sb.append(count).append(" events gaps: ");
for (int i = 0; i < Math.min(count, TRUNCATE_MESSAGE_LENGTH); i++) {
IndexedEventPair info = mEventGaps.get(i);
- sb.append(String.format("position=%d, delta_time=%dns; ", info.index,
- info.event.timestamp - info.previousEvent.timestamp));
+ sb.append(String.format("position=%d, delta_time=%.2fms; ", info.index,
+ nanosToMillis(info.event.timestamp - info.previousEvent.timestamp)));
}
if (count > TRUNCATE_MESSAGE_LENGTH) {
sb.append(count - TRUNCATE_MESSAGE_LENGTH).append(" more; ");
}
- sb.append(String.format("(expected <%dns)",
- TimeUnit.NANOSECONDS.convert((int) (THRESHOLD * mExpectedDelayUs),
- TimeUnit.MICROSECONDS)));
+ sb.append(String.format("(expected <%.2fms)",
+ (double)(THRESHOLD * mExpectedDelayUs)/1000.0));
Assert.fail(sb.toString());
}
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventTimestampSynchronizationVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventTimestampSynchronizationVerification.java
index 3af3f03..6f97439 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventTimestampSynchronizationVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventTimestampSynchronizationVerification.java
@@ -149,9 +149,12 @@
if (eventTimestampNs < lowerThresholdNs || eventTimestampNs > upperThresholdNs) {
if (failures.size() < TRUNCATE_MESSAGE_LENGTH) {
builder.append("position=").append(i);
- builder.append(", timestamp=").append(eventTimestampNs).append("ns");
- builder.append(", expected=[").append(lowerThresholdNs);
- builder.append(", ").append(upperThresholdNs).append("]ns; ");
+ builder.append(", timestamp=").append(String.format("%.2fms",
+ nanosToMillis(eventTimestampNs)));
+ builder.append(", expected=[").append(String.format("%.2fms",
+ nanosToMillis(lowerThresholdNs)));
+ builder.append(", ").append(String.format("%.2f]ms; ",
+ nanosToMillis(upperThresholdNs)));
}
failures.add(new IndexedEvent(i, event));
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES128GCMNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES128GCMNoPaddingCipherTest.java
index 6ae13ce..ed9c2b1 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES128GCMNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES128GCMNoPaddingCipherTest.java
@@ -23,9 +23,14 @@
private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
"6d7596a8fd56ceaec61de7940984b7736fec44f572afc3c8952e4dc6541e2bc6a702c440a37610989543f6"
+ "3fedb047ca2173bc18581944");
- private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ private static final byte[] KAT_CIPHERTEXT_WITHOUT_AAD = HexEncoding.decode(
"b3f6799e8f9326f2df1e80fcd2cb16d78c9dc7cc14bb677862dc6c639b3a6338d24b312d3989e5920b5dbf"
+ "c976765efbfe57bb385940a7a43bdf05bddae3c9d6a2fbbdfcc0cba0");
+ private static final byte[] KAT_AAD = HexEncoding.decode(
+ "d3bc7458914f45d56d5fcfbb2eeff2dcc0e620c1229d90904e98930ea71aa43b6898f846f3244d");
+ private static final byte[] KAT_CIPHERTEXT_WITH_AAD = HexEncoding.decode(
+ "b3f6799e8f9326f2df1e80fcd2cb16d78c9dc7cc14bb677862dc6c639b3a6338d24b312d3989e5920b5dbf"
+ + "c976765efbfe57bb385940a70c106264d81506f8daf9cd6a1c70988c");
@Override
protected byte[] getKatKey() {
@@ -44,6 +49,16 @@
@Override
protected byte[] getKatCiphertext() {
- return KAT_CIPHERTEXT.clone();
+ return KAT_CIPHERTEXT_WITHOUT_AAD.clone();
+ }
+
+ @Override
+ protected byte[] getKatAad() {
+ return KAT_AAD.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertextWhenKatAadPresent() {
+ return KAT_CIPHERTEXT_WITH_AAD.clone();
}
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192CBCNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192CBCNoPaddingCipherTest.java
new file mode 100644
index 0000000..f49c7c3
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192CBCNoPaddingCipherTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES192CBCNoPaddingCipherTest extends AESCBCNoPaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "be8cc4e25cce46e5d55725e2391f7d3cf59ed60062f5a43b");
+ private static final byte[] KAT_IV = HexEncoding.decode("80a199aab0eee77e7762ddf3b3a32f40");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "064f9200e0df37d4711af4a69d11addf9e1c345d9d8195f9f1f715019ce96a167f2497c994bd496eb80bfb"
+ + "2ba2c9d5af");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "859b90becaa85e95a71e104efbd7a3b723bcbf4eb39865544a05d9e90b6fe572c134552f3a138e726fbe49"
+ + "3b3a839598");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return KAT_IV.clone();
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192CBCPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192CBCPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..b17befc
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192CBCPKCS7PaddingCipherTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES192CBCPKCS7PaddingCipherTest extends AESCBCPKCS7PaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "68969215ec41e4df7d23de0e806f458f52aff492bd7c5263");
+ private static final byte[] KAT_IV = HexEncoding.decode("e61d13dfbf0533289f0e7950209da418");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "8d4c1cac27511ee2d82409a7f378e7e402b0eb189c1eaa5c506eb72a9074b170");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "e70bcd62c595dc1b2b8c197bb91a7447e1be2cbcf3fdc69e7e991faf0f57cf4e3884138ff403a41fd99818"
+ + "708ada301c");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return KAT_IV.clone();
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192CTRNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192CTRNoPaddingCipherTest.java
new file mode 100644
index 0000000..d4a4143
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192CTRNoPaddingCipherTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES192CTRNoPaddingCipherTest extends AESCTRNoPaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "5e2036e790d38815c90beb67a1c9e5aa0e167ef082927317");
+ private static final byte[] KAT_IV = HexEncoding.decode("df0694959b89054156962d68a226965c");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "6ed2781c99e03e45314d6019932220c2c98130c53f9f67ad10ac519adf50e928091e09cdbbd3b42b");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "e427b6666502e05b82d0b20ae50e862b1936d71266fc49178ac984e71571f22ae0f90f0c19f42b4a");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return KAT_IV.clone();
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192ECBNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192ECBNoPaddingCipherTest.java
new file mode 100644
index 0000000..0557d03
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192ECBNoPaddingCipherTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES192ECBNoPaddingCipherTest extends AESECBNoPaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "3cab83fb338ba985fbfe74c5e9d2e900adb570b1d67faf92");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "2cc64c335a13fb838f3c6aad0a6b47297ca90bb886ddb059200f0b41740c44ab");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "9c5c825328f5ee0aa24947e374d3f9165f484b39dd808c790d7a129648102453");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return null;
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192ECBPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192ECBPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..1cf193c
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192ECBPKCS7PaddingCipherTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES192ECBPKCS7PaddingCipherTest extends AESECBPKCS7PaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "d57f4e5446f736c16476ec4db5decc7b1bf3936e4f7e4618");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "b115777f1ee7a43a07daa6401e59c46b7a98213a8747eabfbe3ca4ec93524de2c7");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "1e92cd20da08bb5fa174a7a69879d4fc25a155e6af06d75b26c5b450d273c8bb7e3a889dd4a9589098b44a"
+ + "cf1056e7aa");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return null;
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192GCMNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192GCMNoPaddingCipherTest.java
new file mode 100644
index 0000000..66b37d3
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192GCMNoPaddingCipherTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES192GCMNoPaddingCipherTest extends AESGCMNoPaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "21339fc1d011abca65d50ce2365230603fd47d07e8830f6e");
+ private static final byte[] KAT_IV = HexEncoding.decode("d5fb1469a8d81dd75286a418");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "cf776dedf53a828d51a0073db3ef0dd1ee19e2e9e243ce97e95841bb9ad4e3ff52");
+ private static final byte[] KAT_CIPHERTEXT_WITHOUT_AAD = HexEncoding.decode(
+ "3a0d48278111d3296bc663df8a5dbeb2474ea47fd85b608f8d9375d9dcf7de1413ad70fb0e1970669095ad"
+ + "77ebb5974ae8");
+ private static final byte[] KAT_AAD = HexEncoding.decode(
+ "04cdc1d840c17dcfccf78b3d792463740ce0bfdc167b98a632e144cafe9663");
+ private static final byte[] KAT_CIPHERTEXT_WITH_AAD = HexEncoding.decode(
+ "3a0d48278111d3296bc663df8a5dbeb2474ea47fd85b608f8d9375d9dcf7de141380718b9f6c023fb091c7"
+ + "6891a91683e2");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return KAT_IV.clone();
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT_WITHOUT_AAD.clone();
+ }
+
+ @Override
+ protected byte[] getKatAad() {
+ return KAT_AAD.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertextWhenKatAadPresent() {
+ return KAT_CIPHERTEXT_WITH_AAD.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256CBCNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256CBCNoPaddingCipherTest.java
new file mode 100644
index 0000000..b6620ef
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256CBCNoPaddingCipherTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES256CBCNoPaddingCipherTest extends AESCBCNoPaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "dd2f20dc6b98c100bac919120ff95eb5d96003f8229987b283a1e777b0cd5c30");
+ private static final byte[] KAT_IV = HexEncoding.decode("23b4d85239fb90db93b07a981e90a170");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "2fbe5d46dca5cea433e550d8b291740ab9551c2a2d37680d7fb7b993225f58494cb53caca353e4b637ba05"
+ + "687be20f8d");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "5aba24fc316936c8369061ee8fe463e4faed04288e204456626b988c0e376b6047da1e4fd7c4e1cf265609"
+ + "7f75ae8685");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return KAT_IV.clone();
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256CBCPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256CBCPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..6613463
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256CBCPKCS7PaddingCipherTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES256CBCPKCS7PaddingCipherTest extends AESCBCPKCS7PaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "03ab2510520f5cfebfab0a17a7f8324c9634911f6fc59e586f85346bb38ac88a");
+ private static final byte[] KAT_IV = HexEncoding.decode("9af96967195bb0184f129beffa8241ae");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "2d6944653ac14988a772a2730b7c5bfa99a21732ae26f40cdc5b3a2874c7942545a82b73c48078b9dae622"
+ + "61c65909");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "26b308f7e1668b55705a79c8b3ad10e244655f705f027f390a5c34e4536f519403a71987b95124073d69f2"
+ + "a3cb95b0ab");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return KAT_IV.clone();
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256CTRNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256CTRNoPaddingCipherTest.java
new file mode 100644
index 0000000..bdcff41
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256CTRNoPaddingCipherTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES256CTRNoPaddingCipherTest extends AESCTRNoPaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "928b380a8fed4b4b4cfeb56e0c66a4cb0f9ff58d61ac68bcfd0e3fbd910a684f");
+ private static final byte[] KAT_IV = HexEncoding.decode("0b678a5249e6eeda461dfb4776b6c58e");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "f358de57543b297e997cba46fb9100553d6abd65377e55b9aac3006400ead11f6db3c884");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "a07a35fbd1776ad81462e1935f542337add60962bf289249476817b6ddd532a7be30d4c3");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return KAT_IV.clone();
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256ECBNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256ECBNoPaddingCipherTest.java
new file mode 100644
index 0000000..847a767
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256ECBNoPaddingCipherTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES256ECBNoPaddingCipherTest extends AESECBNoPaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "fa4622d9cf6485075daedd33d2c4fffdf859e2edb7f7df4f04603f7e647fae90");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "96ccabbe0c68970d8cdee2b30ab43c2d61cc50ee68271e77571e72478d713a31a476d6806b8116089c6ec5"
+ + "0bb543200f");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "0e81839e9dfbfe3b503d619e676abe5ac80fac3f245d8f09b9134b1b32a67dc83e377faf246288931136be"
+ + "f2a07c0be4");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return null;
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256ECBPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256ECBPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..0faffe9
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256ECBPKCS7PaddingCipherTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES256ECBPKCS7PaddingCipherTest extends AESECBPKCS7PaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "bf3f07c68467fead0ca8e2754500ab514258abf02eb7e615a493bcaaa45d5ee1");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "af0757e49018dad628f16998628a407db5f28291bef3bc2e4d8a5a31fb238e6f");
+ private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
+ "21ec3011074bf1ef140643d47130326c5e183f61237c69bc77551ca207d71fc2b90cfac6c8d2d125e5cd9f"
+ + "f353dee0df");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return null;
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256GCMNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256GCMNoPaddingCipherTest.java
new file mode 100644
index 0000000..971e610
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256GCMNoPaddingCipherTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class AES256GCMNoPaddingCipherTest extends AESGCMNoPaddingCipherTestBase {
+
+ private static final byte[] KAT_KEY = HexEncoding.decode(
+ "7972140d831eedac75d5ea515c9a4c3bb124499a90b5f317ac1a685e88fae395");
+ private static final byte[] KAT_IV = HexEncoding.decode("a66c5252808d823dd4151fed");
+ private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
+ "c2b9dabf3a55adaa94e8c0d1e77a84a3435aee23b2c3c4abb587b09a9c2afbf0");
+ private static final byte[] KAT_CIPHERTEXT_WITHOUT_AAD = HexEncoding.decode(
+ "a960619314657b2afb96b93bebb372bffd09e19d53e351f17d1ba2611f9dc33c9c92d563e8fd381254ac26"
+ + "2aa2a4ea0d");
+ private static final byte[] KAT_AAD = HexEncoding.decode(
+ "3727229db7a3ccda7283f628fb8a3cdf093ea1f4e8bd1bc40a830fc6df6fb0e249845dd7d449b2bc3b5ba4"
+ + "2258fb92c7");
+ private static final byte[] KAT_CIPHERTEXT_WITH_AAD = HexEncoding.decode(
+ "a960619314657b2afb96b93bebb372bffd09e19d53e351f17d1ba2611f9dc33c1501caa6cca0a281f42bc3"
+ + "10d1e4488f");
+
+ @Override
+ protected byte[] getKatKey() {
+ return KAT_KEY.clone();
+ }
+
+ @Override
+ protected byte[] getKatIv() {
+ return KAT_IV.clone();
+ }
+
+ @Override
+ protected byte[] getKatPlaintext() {
+ return KAT_PLAINTEXT.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertext() {
+ return KAT_CIPHERTEXT_WITHOUT_AAD.clone();
+ }
+
+ @Override
+ protected byte[] getKatAad() {
+ return KAT_AAD.clone();
+ }
+
+ @Override
+ protected byte[] getKatCiphertextWhenKatAadPresent() {
+ return KAT_CIPHERTEXT_WITH_AAD.clone();
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AESECBCipherTestBase.java b/tests/tests/keystore/src/android/keystore/cts/AESECBCipherTestBase.java
index 5ecf22f..b2752dc 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AESECBCipherTestBase.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AESECBCipherTestBase.java
@@ -54,4 +54,8 @@
}
return null;
}
+
+ public void testInitRejectsIvParameterSpec() throws Exception {
+ assertInitRejectsIvParameterSpec(new byte[getBlockSize()]);
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AESGCMCipherTestBase.java b/tests/tests/keystore/src/android/keystore/cts/AESGCMCipherTestBase.java
index d901674..1c3404a 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AESGCMCipherTestBase.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AESGCMCipherTestBase.java
@@ -16,14 +16,21 @@
package android.keystore.cts;
+import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
+import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
+import javax.crypto.AEADBadTagException;
+import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
abstract class AESGCMCipherTestBase extends BlockCipherTestBase {
+ protected abstract byte[] getKatAad();
+ protected abstract byte[] getKatCiphertextWhenKatAadPresent();
+
@Override
protected boolean isStreamCipher() {
return true;
@@ -54,4 +61,148 @@
GCMParameterSpec spec = params.getParameterSpec(GCMParameterSpec.class);
return spec.getIV();
}
+
+ public void testKatEncryptWithAadProvidedInOneGo() throws Exception {
+ createCipher();
+ assertKatTransformWithAadProvidedInOneGo(
+ Cipher.ENCRYPT_MODE,
+ getKatAad(),
+ getKatPlaintext(),
+ getKatCiphertextWhenKatAadPresent());
+ }
+
+ public void testKatDecryptWithAadProvidedInOneGo() throws Exception {
+ createCipher();
+ assertKatTransformWithAadProvidedInOneGo(
+ Cipher.DECRYPT_MODE,
+ getKatAad(),
+ getKatCiphertextWhenKatAadPresent(),
+ getKatPlaintext());
+ }
+
+ public void testKatEncryptWithAadProvidedInChunks() throws Exception {
+ createCipher();
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.ENCRYPT_MODE,
+ getKatAad(),
+ getKatPlaintext(),
+ getKatCiphertextWhenKatAadPresent(),
+ 1);
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.ENCRYPT_MODE,
+ getKatAad(),
+ getKatPlaintext(),
+ getKatCiphertextWhenKatAadPresent(),
+ 8);
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.ENCRYPT_MODE,
+ getKatAad(),
+ getKatPlaintext(),
+ getKatCiphertextWhenKatAadPresent(),
+ 3);
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.ENCRYPT_MODE,
+ getKatAad(),
+ getKatPlaintext(),
+ getKatCiphertextWhenKatAadPresent(),
+ 7);
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.ENCRYPT_MODE,
+ getKatAad(),
+ getKatPlaintext(),
+ getKatCiphertextWhenKatAadPresent(),
+ 23);
+ }
+
+ public void testKatDecryptWithAadProvidedInChunks() throws Exception {
+ createCipher();
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.DECRYPT_MODE,
+ getKatAad(),
+ getKatCiphertextWhenKatAadPresent(),
+ getKatPlaintext(),
+ 1);
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.DECRYPT_MODE,
+ getKatAad(),
+ getKatCiphertextWhenKatAadPresent(),
+ getKatPlaintext(),
+ 8);
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.DECRYPT_MODE,
+ getKatAad(),
+ getKatCiphertextWhenKatAadPresent(),
+ getKatPlaintext(),
+ 3);
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.DECRYPT_MODE,
+ getKatAad(),
+ getKatCiphertextWhenKatAadPresent(),
+ getKatPlaintext(),
+ 7);
+ assertKatTransformWithAadProvidedInChunks(
+ Cipher.DECRYPT_MODE,
+ getKatAad(),
+ getKatCiphertextWhenKatAadPresent(),
+ getKatPlaintext(),
+ 23);
+ }
+
+ private void assertKatTransformWithAadProvidedInOneGo(int opmode,
+ byte[] aad, byte[] input, byte[] expectedOutput) throws Exception {
+ initKat(opmode);
+ updateAAD(aad);
+ assertEquals(expectedOutput, doFinal(input));
+
+ initKat(opmode);
+ updateAAD(aad, 0, aad.length);
+ assertEquals(expectedOutput, doFinal(input));
+
+ initKat(opmode);
+ updateAAD(ByteBuffer.wrap(aad));
+ assertEquals(expectedOutput, doFinal(input));
+ }
+
+ private void assertKatTransformWithAadProvidedInChunks(int opmode,
+ byte[] aad, byte[] input, byte[] expectedOutput, int maxChunkSize) throws Exception {
+ createCipher();
+ initKat(opmode);
+ int aadOffset = 0;
+ while (aadOffset < aad.length) {
+ int chunkSize = Math.min(aad.length - aadOffset, maxChunkSize);
+ updateAAD(aad, aadOffset, chunkSize);
+ aadOffset += chunkSize;
+ }
+ assertEquals(expectedOutput, doFinal(input));
+ }
+
+ public void testCiphertextBitflipDetectedWhenDecrypting() throws Exception {
+ createCipher();
+ Key key = importKey(getKatKey());
+ byte[] ciphertext = getKatCiphertext();
+ ciphertext[ciphertext.length / 2] ^= 0x40;
+ init(Cipher.DECRYPT_MODE, key, getKatAlgorithmParameterSpec());
+ try {
+ doFinal(ciphertext);
+ fail();
+ } catch (AEADBadTagException expected) {}
+ }
+
+ public void testAadBitflipDetectedWhenDecrypting() throws Exception {
+ createCipher();
+ Key key = importKey(getKatKey());
+ byte[] ciphertext = getKatCiphertextWhenKatAadPresent();
+ byte[] aad = getKatCiphertext();
+ aad[aad.length / 3] ^= 0x2;
+ init(Cipher.DECRYPT_MODE, key, getKatAlgorithmParameterSpec());
+ updateAAD(aad);
+ try {
+ doFinal(ciphertext);
+ fail();
+ } catch (AEADBadTagException expected) {}
+ }
+
+ public void testInitRejectsIvParameterSpec() throws Exception {
+ assertInitRejectsIvParameterSpec(getKatIv());
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java b/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java
index 398d373..f583c51 100644
--- a/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java
+++ b/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java
@@ -45,6 +45,7 @@
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
abstract class BlockCipherTestBase extends AndroidTestCase {
@@ -828,21 +829,33 @@
}
public void testUpdateAADNotSupported() throws Exception {
- createCipher();
- initKat(Cipher.ENCRYPT_MODE);
if (isAuthenticatedCipher()) {
- assertUpdateAADSupported();
- } else {
- assertUpdateAADNotSupported();
+ // Not applicable to authenticated ciphers where updateAAD is supported.
+ return;
}
createCipher();
+ initKat(Cipher.ENCRYPT_MODE);
+ assertUpdateAADNotSupported();
+
+ createCipher();
initKat(Cipher.DECRYPT_MODE);
- if (isAuthenticatedCipher()) {
- assertUpdateAADSupported();
- } else {
- assertUpdateAADNotSupported();
+ assertUpdateAADNotSupported();
+ }
+
+ public void testUpdateAADSupported() throws Exception {
+ if (!isAuthenticatedCipher()) {
+ // Not applicable to unauthenticated ciphers where updateAAD is not supported.
+ return;
}
+
+ createCipher();
+ initKat(Cipher.ENCRYPT_MODE);
+ assertUpdateAADSupported();
+
+ createCipher();
+ initKat(Cipher.DECRYPT_MODE);
+ assertUpdateAADSupported();
}
private void assertUpdateAADNotSupported() throws Exception {
@@ -1235,7 +1248,7 @@
subarray(buffer, outputOffsetInBuffer, outputEndIndexInBuffer));
}
- private void createCipher() throws NoSuchAlgorithmException,
+ protected void createCipher() throws NoSuchAlgorithmException,
NoSuchPaddingException {
mCipher = Cipher.getInstance(getTransformation());
}
@@ -1279,7 +1292,7 @@
return importKey(getKatKey());
}
- private SecretKey importKey(byte[] keyMaterial) {
+ protected SecretKey importKey(byte[] keyMaterial) {
try {
int keyId = mNextKeyId++;
String keyAlias = "key" + keyId;
@@ -1318,75 +1331,75 @@
}
}
- private void initKat(int opmode)
+ protected void initKat(int opmode)
throws InvalidKeyException, InvalidAlgorithmParameterException {
init(opmode, getKey(), getKatAlgorithmParameterSpec());
}
- private void init(int opmode, Key key, AlgorithmParameters spec)
+ protected void init(int opmode, Key key, AlgorithmParameters spec)
throws InvalidKeyException, InvalidAlgorithmParameterException {
mCipher.init(opmode, key, spec);
mOpmode = opmode;
}
- private void init(int opmode, Key key, AlgorithmParameters spec, SecureRandom random)
+ protected void init(int opmode, Key key, AlgorithmParameters spec, SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
mCipher.init(opmode, key, spec, random);
mOpmode = opmode;
}
- private void init(int opmode, Key key, AlgorithmParameterSpec spec)
+ protected void init(int opmode, Key key, AlgorithmParameterSpec spec)
throws InvalidKeyException, InvalidAlgorithmParameterException {
mCipher.init(opmode, key, spec);
mOpmode = opmode;
}
- private void init(int opmode, Key key, AlgorithmParameterSpec spec, SecureRandom random)
+ protected void init(int opmode, Key key, AlgorithmParameterSpec spec, SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
mCipher.init(opmode, key, spec, random);
mOpmode = opmode;
}
- private void init(int opmode, Key key) throws InvalidKeyException {
+ protected void init(int opmode, Key key) throws InvalidKeyException {
mCipher.init(opmode, key);
mOpmode = opmode;
}
- private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
+ protected void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
mCipher.init(opmode, key, random);
mOpmode = opmode;
}
- private byte[] doFinal() throws IllegalBlockSizeException, BadPaddingException {
+ protected byte[] doFinal() throws IllegalBlockSizeException, BadPaddingException {
return mCipher.doFinal();
}
- private byte[] doFinal(byte[] input) throws IllegalBlockSizeException, BadPaddingException {
+ protected byte[] doFinal(byte[] input) throws IllegalBlockSizeException, BadPaddingException {
return mCipher.doFinal(input);
}
- private byte[] doFinal(byte[] input, int inputOffset, int inputLen)
+ protected byte[] doFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
return mCipher.doFinal(input, inputOffset, inputLen);
}
- private int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output)
+ protected int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
return mCipher.doFinal(input, inputOffset, inputLen, output);
}
- private int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
+ protected int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
return mCipher.doFinal(input, inputOffset, inputLen, output, outputOffset);
}
- private int doFinal(byte[] output, int outputOffset) throws IllegalBlockSizeException,
+ protected int doFinal(byte[] output, int outputOffset) throws IllegalBlockSizeException,
ShortBufferException, BadPaddingException {
return mCipher.doFinal(output, outputOffset);
}
- private int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException,
+ protected int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException,
IllegalBlockSizeException, BadPaddingException {
return mCipher.doFinal(input, output);
}
@@ -1415,21 +1428,21 @@
}
}
- private byte[] update(byte[] input) {
+ protected byte[] update(byte[] input) {
byte[] output = mCipher.update(input);
assertUpdateOutputSize(
(input != null) ? input.length : 0, (output != null) ? output.length : 0);
return output;
}
- private byte[] update(byte[] input, int offset, int len) {
+ protected byte[] update(byte[] input, int offset, int len) {
byte[] output = mCipher.update(input, offset, len);
assertUpdateOutputSize(len, (output != null) ? output.length : 0);
return output;
}
- private int update(byte[] input, int offset, int len, byte[] output)
+ protected int update(byte[] input, int offset, int len, byte[] output)
throws ShortBufferException {
int outputLen = mCipher.update(input, offset, len, output);
assertUpdateOutputSize(len, outputLen);
@@ -1437,7 +1450,7 @@
return outputLen;
}
- private int update(byte[] input, int offset, int len, byte[] output, int outputOffset)
+ protected int update(byte[] input, int offset, int len, byte[] output, int outputOffset)
throws ShortBufferException {
int outputLen = mCipher.update(input, offset, len, output, outputOffset);
assertUpdateOutputSize(len, outputLen);
@@ -1445,7 +1458,7 @@
return outputLen;
}
- private int update(ByteBuffer input, ByteBuffer output) throws ShortBufferException {
+ protected int update(ByteBuffer input, ByteBuffer output) throws ShortBufferException {
int inputLimitBefore = input.limit();
int outputLimitBefore = output.limit();
int inputLen = input.remaining();
@@ -1463,8 +1476,20 @@
return outputLen;
}
+ protected void updateAAD(byte[] input) {
+ mCipher.updateAAD(input);
+ }
+
+ protected void updateAAD(byte[] input, int offset, int len) {
+ mCipher.updateAAD(input, offset, len);
+ }
+
+ protected void updateAAD(ByteBuffer input) {
+ mCipher.updateAAD(input);
+ }
+
@SuppressWarnings("unused")
- private static void assertEquals(Buffer expected, Buffer actual) {
+ protected static void assertEquals(Buffer expected, Buffer actual) {
throw new RuntimeException(
"Comparing ByteBuffers using their .equals is probably not what you want"
+ " -- use assertByteBufferEquals instead.");
@@ -1474,7 +1499,7 @@
* Asserts that the position, limit, and capacity of the provided buffers are the same, and that
* their contents (from position {@code 0} to capacity) are the same.
*/
- private static void assertByteBufferEquals(ByteBuffer expected, ByteBuffer actual) {
+ protected static void assertByteBufferEquals(ByteBuffer expected, ByteBuffer actual) {
if (expected == null) {
if (actual == null) {
return;
@@ -1504,7 +1529,7 @@
buffer.array(), buffer.arrayOffset(), buffer.capacity()) + "]";
}
- private static boolean equals(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2,
+ protected static boolean equals(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2,
int len2) {
if (arr1 == null) {
return (arr2 == null);
@@ -1523,13 +1548,13 @@
}
}
- private static byte[] subarray(byte[] array, int beginIndex, int endIndex) {
+ protected static byte[] subarray(byte[] array, int beginIndex, int endIndex) {
byte[] result = new byte[endIndex - beginIndex];
System.arraycopy(array, beginIndex, result, 0, result.length);
return result;
}
- private static byte[] concat(byte[]... arrays) {
+ protected static byte[] concat(byte[]... arrays) {
int resultLength = 0;
for (byte[] array : arrays) {
resultLength += (array != null) ? array.length : 0;
@@ -1546,11 +1571,11 @@
return result;
}
- private static void assertEquals(byte[] expected, byte[] actual) {
+ protected static void assertEquals(byte[] expected, byte[] actual) {
assertEquals(null, expected, actual);
}
- private static void assertEquals(String message, byte[] expected, byte[] actual) {
+ protected static void assertEquals(String message, byte[] expected, byte[] actual) {
if (!Arrays.equals(expected, actual)) {
StringBuilder detail = new StringBuilder();
if (expected != null) {
@@ -1572,4 +1597,51 @@
}
}
}
+
+ protected final void assertInitRejectsIvParameterSpec(byte[] iv) throws Exception {
+ Key key = importKey(getKatKey());
+ createCipher();
+ IvParameterSpec spec = new IvParameterSpec(iv);
+ try {
+ init(Cipher.ENCRYPT_MODE, key, spec);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+
+ try {
+ init(Cipher.WRAP_MODE, key, spec);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+
+ try {
+ init(Cipher.DECRYPT_MODE, key, spec);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+
+ try {
+ init(Cipher.UNWRAP_MODE, key, spec);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+
+ AlgorithmParameters param = AlgorithmParameters.getInstance("AES");
+ param.init(new IvParameterSpec(iv));
+ try {
+ init(Cipher.ENCRYPT_MODE, key, param);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+
+ try {
+ init(Cipher.WRAP_MODE, key, param);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+
+ try {
+ init(Cipher.DECRYPT_MODE, key, param);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+
+ try {
+ init(Cipher.UNWRAP_MODE, key, param);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
new file mode 100644
index 0000000..0300ec6
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+import com.android.cts.keystore.R;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.Provider;
+import java.security.Security;
+import java.security.interfaces.RSAKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.Provider.Service;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Tests for algorithm-agnostic functionality of {@code Cipher} implementations backed by Android
+ * Keystore.
+ */
+public class CipherTest extends AndroidTestCase {
+
+ private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
+
+ private static final String[] EXPECTED_ALGORITHMS = {
+ "AES/ECB/NoPadding",
+ "AES/ECB/PKCS7Padding",
+ "AES/CBC/NoPadding",
+ "AES/CBC/PKCS7Padding",
+ "AES/CTR/NoPadding",
+ "AES/GCM/NoPadding",
+ "RSA/ECB/NoPadding",
+ "RSA/ECB/PKCS1Padding",
+ "RSA/ECB/OAEPPadding",
+ "RSA/ECB/OAEPWithSHA-1AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-224AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-384AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-512AndMGF1Padding",
+ };
+
+ private static class KatVector {
+ private final byte[] plaintext;
+ private final byte[] ciphertext;
+ private final AlgorithmParameterSpec params;
+
+ private KatVector(String plaintextHex, String ciphertextHex) {
+ this(plaintextHex, null, ciphertextHex);
+ }
+
+ private KatVector(String plaintextHex, AlgorithmParameterSpec params,
+ String ciphertextHex) {
+ this(HexEncoding.decode(plaintextHex), params, HexEncoding.decode(ciphertextHex));
+ }
+
+ private KatVector(byte[] plaintext, byte[] ciphertext) {
+ this(plaintext, null, ciphertext);
+ }
+
+ private KatVector(byte[] plaintext, AlgorithmParameterSpec params, byte[] ciphertext) {
+ this.plaintext = plaintext;
+ this.ciphertext = ciphertext;
+ this.params = params;
+ }
+ }
+ private static final Map<String, KatVector> KAT_VECTORS =
+ new TreeMap<String, KatVector>(String.CASE_INSENSITIVE_ORDER);
+ static {
+ // From RI
+ KAT_VECTORS.put("AES/ECB/NoPadding", new KatVector(
+ "0383911bb1519d58e6656f3fd35639c502dbeb2196cea937fca272666cb4a80b",
+ "6574c5065283b89e0c930019e4655d8516b98170db6516cd83e589bd9c5e5adc"));
+ KAT_VECTORS.put("AES/ECB/PKCS7Padding", new KatVector(
+ "1ad3d73a3cfa66dac78a51a95c2cb2125ea701e6e9ecbca2415b436f0258e2ba7439b67545",
+ "920f873f2f9e91bac4c9c948d66496a21b8b2606850490dac7abecae83317488ee550b9973ac5cd142"
+ + "f387d7d2a12752"));
+ KAT_VECTORS.put("AES/CBC/NoPadding", new KatVector(
+ "1dffe21c8f18276c3a39ed0c53ab257b84efcedab60095c4cadd131143058cf7",
+ new IvParameterSpec(HexEncoding.decode("10b3eea6cc8a7d6f48337e9b6987d28c")),
+ "47ab115bfadca91eaebec73ab942a06f3121fdd5aa55d223bd2cbcc3855e1ef8"));
+ KAT_VECTORS.put("AES/CBC/PKCS7Padding", new KatVector(
+ "9d49fb970b23bfe742ae7c45a773ada9faad84708c8858a06e4a192e0a90e2f6083548e0bf3f67",
+ new IvParameterSpec(HexEncoding.decode("ecd87bf9c49f37dcd2294e309192289a")),
+ "aeb64f48ec18a086eda7ee080948651a50b6f582ab54aac5454c9ab0a4de5b4a4abac526a4307011d1"
+ + "2881f1849c32ae"));
+ KAT_VECTORS.put("AES/CTR/NoPadding", new KatVector(
+ "b4e786cab9df48d2fce0c7872651314db1318d1f31a1b10a2c334d2555b4117668",
+ new IvParameterSpec(HexEncoding.decode("94d9f7a6d16f58018819b668020b68cc")),
+ "022e74572a70be57a0b65b2fb5bc9b803ce48973b6163f528bbe1fd001e29d330a"));
+ KAT_VECTORS.put("AES/GCM/NoPadding", new KatVector(
+ "03889a6ca811e3fd7e78467e3dae587d2110e80e98edbc9dfe17afba238c4c493186",
+ new GCMParameterSpec(128, HexEncoding.decode("f67aaf97cdec65b12188315e")),
+ "159eb1ffc86589b38f18097c32db646c7de3525b603876c3ae671bc2ca52a5395a374b377a915c9ed1"
+ + "a349abf9fc54c9ca81"));
+ KAT_VECTORS.put("RSA/ECB/NoPadding", new KatVector(
+ "50c499d558c38fd48ea76832887db2abc76e4e153a98fd4323ccb8006d34f11724a5692fb101b0eb96"
+ + "060eb9d15222",
+ "349b1d5061e98d0ab3f2327680bbc0cbb1b8ef8ee26148d7c67cf535223e3f78d822d369592ede29b1"
+ + "654aab25e6ae5e098318e55c13dc405f5ba27e5cc69ced32778592a51e6293a03f95e14ed17099fb"
+ + "0ac585e41297b87c3432953df0d98be7e505dc7de7bfe9d9ec750f475afeba4cc2dd78838c0d4399"
+ + "d8de02b07f00b292dc3d32d2a2f98ea5a5dac1a0fec4d01e5c3aea8c56eeff264896fb6cf2144401"
+ + "278c6663417bc00aafbb9eb97c056573cdec88d6ac6fd6c333d131337b16031da229029e3b6fe6f8"
+ + "ee427f2e90041e9636d67cddac75845914ce4be56092eed7188fe7e2bb33769efdeed86a7acbe15d"
+ + "debf92d9fbaaddede206acfa650697"));
+ KAT_VECTORS.put("RSA/ECB/PKCS1Padding", new KatVector(
+ "aed8cd94f35b2a54cdd3ed771482bd87e256b995408558fb82e5d475d1ee54711472f899ad6cbb6847"
+ + "99e52ff1d57cbc39f4",
+ "64148dee294dd3ea31d2b595ea661318cf90c89f71393cf6559087d6e8993e73eb1e6b5f4d3cfde3cb"
+ + "267938c5eca522b95a2df02df9c703dbe3103c157af0d2ed5b70da51cb4caa49061319420d0ea433"
+ + "f24b727530c162226bc806b7f39079cd494a5c8a242737413d27063f9fb74aadd20f521211316719"
+ + "c628fd4351d0608928949b6f59f351d9ccec4c596514335010834fcabd53a2cbb2642e0f83c4f89c"
+ + "199ee2c68ace9182cf484d99e86b0b2213c1cc113d24891958e5a0774b7486abae1475e46a939a94"
+ + "5d6491b98ad7979fd6e752b47e43e960557a0c0589d7d0444b011d75c9f5b143da6e1dcf7b678a2e"
+ + "f82fbe37a74df3e20fb1a9dbfd5978"));
+ KAT_VECTORS.put("RSA/ECB/OAEPPadding", new KatVector(
+ "c219f4e3e37eae2315f0fa4ebc4b46ef0c6befbb43a51ceda07435fc88a9",
+ "7a9bcfd0d02b6434025bbf5ba09c2dad118a4a3bca7cced8b404bc0fc2f17ddee13de82c8324294bf2"
+ + "60ad6e5171c2c3728a0c0fab20dd60e4e56cfef3e66239439ed2eddcc83ac8eeaedfd970e9966de3"
+ + "94ad1df0df503a0a640a49e10885b3a4115c3e94e893fff87bf9a5808350f957d6bc556ca6b08f81"
+ + "bf697704a3eb3db774797f883af0dcdc9bd9196d7595bab5e87d3187eb45b5771abe4e4dc70c25fa"
+ + "b9e3cddb6ae453a1d8e517d000779472e1376e5848b1654a51a9e90be4a4a6d0f6b8723c6e93c471"
+ + "313ea94f24504ca377b502057331355965a7e0b9c3b1d1fbd24ab5a4167f721d1ddac4d3c094d5c9"
+ + "0d2e277e9b5617cbf2770186323e89"));
+ KAT_VECTORS.put("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", new KatVector(
+ "bb2854620bb0e361d1384703dda12acee1fefc22024bcfc40a86390d5342c693aab8c7ed6517d8da86"
+ + "04492c9d",
+ "77033c578f24ef0ed93bfe6dc6f7c3f9f0505e7562f67ce987a269cabaa8a3ae7dd5e567a8b37db42d"
+ + "a79aa86ea2e189af5b9560b39407ff86f2785cdaf660fc7c93649bc24a818de564cb0d03e7681fa8"
+ + "f3cd42b3bfc58c49d3f049e0c98b07aff95876f05ddc45ebaa7127a198f27ae0cfd161c5598ac795"
+ + "8ed386d98b13d45730e6dc16313fe012af27d7be0e45215040bbfb07f2d35e34291fe4335a68175a"
+ + "46be99a15c1ccf673659157e1f52105de5a0a6f8c9d946740216eefe2a01a37b0ab144a44ff0d800"
+ + "be713b5b44acf4fcb1a60d5db977af4d77fa77bdb8594032b2f5bbdd49346b08e0e98ab1051b462e"
+ + "160c1bff62b927cd26c936948b723a"));
+ KAT_VECTORS.put("RSA/ECB/OAEPWithSHA-224AndMGF1Padding", new KatVector(
+ "1bae19434be6599d1987b1ed866dd6b684dcd908bd98d797250be545eafea46d05ebdf9018",
+ "0f18b4a1153c6f8821e18a4275e4b570d540c8ad86bfc99146e5475238a43ecbe63bc81368cd64b9a2"
+ + "ab3ccd586e6afaad054c9d7bdc986adf022ec86335d110c53ebd5f2f2bd49d48d6da9541312c9b1b"
+ + "cc299ca4f59475869e4ec2253c91b137eae274a245fc9ee6262f74754bbda55d8bd25bfa4c1698f3"
+ + "a22d2d8d7fc6e9fbb56d828e61912b3085d82cceaeb1d2da425871575e7ba31a3d47b1b7d7df0bda"
+ + "81d62c75a9887bbc528fc6bb51db09884bb513b4cc94ca4a5fe0b370ca548dcdf60eebbf61e7efe7"
+ + "630fc47256d6d617fc1c2c774405f385650898abea03502cfbdcb53579fd18d896490e67aecdb7c7"
+ + "b7b950dc7ddba5c64188494c1a177b"));
+ KAT_VECTORS.put("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", new KatVector(
+ "332c2f2fc066fb29ec0928a52b5111ce6965546ce73927340c42d33b56b6ba547b77ac361ac0d13316"
+ + "345ca953840023d892fa4ff1aa32cc66d5aa88b79867",
+ "942c0ba1c67a34a7e116d9281b1df5084c66bc1458faf1b26d4f0f63a57307a9addcd3e5d2f3320071"
+ + "5a3d95ae84fb40a8dfe4cb0a28873fd5883ff8ee6efbfe38c460c755577b34fcf05bb2077afec7b2"
+ + "203799022be6a0903915e01e94abc51efe9c5548eb86bbbb4fd7f3bfc7b86f388128b6df1e6ce651"
+ + "230c6bc18bbf55b029f1e31da880c27d947ff97519df66a57ead6db791c4978f1d62edec0d89bb16"
+ + "83d237213f3f24271ddb8c4b50a82527954f0e49ae44d3acd8ddd3a57cfbfa456dd40675d5d75542"
+ + "31c6b79c7fb3500b1631be1d100e67d85ce423845fdc7c7f45e346a8ba573f5d11de9009069468dd"
+ + "8d517ad4adb1509dd5173ee1862d74"));
+ KAT_VECTORS.put("RSA/ECB/OAEPWithSHA-384AndMGF1Padding", new KatVector(
+ "f51f158cbad4dbab38403b839c724f09a480c49be29c0e72615539dbe57ec86143f31f19392f419b5b"
+ + "e4ba9e3c6f1e870d307a7cf1a9e2",
+ "944243f35f534e7a273e94986b6835a4f5cdc5bc4efb9970d4760986599a02f652a848fcae333ff25a"
+ + "64108c9b900aaf002688398ad9fc17c73be52726306af9c13540df9d1765336b6f09ba4cb8a54d72"
+ + "5a4e45854bfa3802cfb110a6d7f7054e6072440ec00da62828cb75fe2566ec5be79eb8a3d1fbe2c2"
+ + "4439c107e5018e445e201ad80725755543c00dec50bb464c6ca897600eb3cda51fcef8161ac13d75"
+ + "a3eb30d385a1e718a61ae1b5d47aadb966fc007becc84db397d0b3cd983121872f9975995153e869"
+ + "9e24554a3c5e885f0ed8cd03e916da5ed541f1598da9bd6209447301d00f086153da353deff9d045"
+ + "8976ff7570410f0bdcfb3f56b782f5"));
+ KAT_VECTORS.put("RSA/ECB/OAEPWithSHA-512AndMGF1Padding", new KatVector(
+ "d45f6ccc7e663957f234c237c1f09bf7791f6f5c1b9ef4fefb16e55ded0d96112e590f1bb08a60f85c"
+ + "2d0d2533f1d69792dfd8d647d880b18f87cfe32488c73613a3d535da7d776d90d9a4ba6a0311f456"
+ + "8511da49107c",
+ "5a037df3e5d6f3f703541e2db2aef7c69985e513bdff67c8ade6a09f50e27267bfb444f6c69b40a77a"
+ + "9136a27b29876af9d2bf4e7099863445d35b188d31f376b89fbd196059667ca657e10b9454c2b25f"
+ + "046fc9f7b42506e382e6b6fd99409cf97e865e65f8dce5d14a06b8aa8833c4bc72c8764467758f2d"
+ + "7960243161dce4ca8231e91bfcd3c933a80bc703ceab976224c876b1f550f91a6c2a0332d4377bd8"
+ + "dfe4b1283ab114e517b7b9e4a6e0bf166d5b506e7a3b7328078e12cb23b1d938760767dc9b3c3eb0"
+ + "848ddda101792aca9273ad414314c13fc511ffa0358a8f4c5f38edded3a2dc111fa62c80e6032c32"
+ + "ae04aeac7729f16a6310f1f6785c27"));
+ }
+
+ private static final long DAY_IN_MILLIS = TestUtils.DAY_IN_MILLIS;
+
+ private static final byte[] AES_KAT_KEY_BYTES =
+ HexEncoding.decode("7d9f11a0da111e9d8bdd14f04648ed91");
+
+ private static final KeyProtection.Builder GOOD_IMPORT_PARAMS_BUILDER =
+ new KeyProtection.Builder(
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_ECB,
+ KeyProperties.BLOCK_MODE_CBC,
+ KeyProperties.BLOCK_MODE_CTR,
+ KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setDigests(KeyProperties.DIGEST_NONE)
+ .setRandomizedEncryptionRequired(false);
+
+ public void testAlgorithmList() {
+ // Assert that Android Keystore Provider exposes exactly the expected Cipher
+ // transformations. We don't care whether the transformations are exposed via aliases, as
+ // long as canonical names of transformation are accepted.
+ // If the Provider exposes extraneous algorithms, it'll be caught because it'll have to
+ // expose at least one Service for such an algorithm, and this Service's algorithm will
+ // not be in the expected set.
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ Set<Service> services = provider.getServices();
+ Set<String> actualAlgsLowerCase = new HashSet<String>();
+ Set<String> expectedAlgsLowerCase = new HashSet<String>(
+ Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS)));
+ for (Service service : services) {
+ if ("Cipher".equalsIgnoreCase(service.getType())) {
+ String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
+ actualAlgsLowerCase.add(algLowerCase);
+ }
+ }
+
+ TestUtils.assertContentsInAnyOrder(actualAlgsLowerCase,
+ expectedAlgsLowerCase.toArray(new String[0]));
+ }
+
+ public void testAndroidKeyStoreKeysHandledByAndroidKeyStoreProviderWhenDecrypting()
+ throws Exception {
+ Collection<SecretKey> secretKeys = importDefaultKatSecretKeys();
+ Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ // Decryption may need additional parameters. Initializing a Cipher for encryption
+ // forces it to generate any such parameters.
+ Cipher cipher = Cipher.getInstance(algorithm, provider);
+ cipher.init(Cipher.ENCRYPT_MODE, getEncryptionKey(algorithm, secretKeys, keyPairs));
+ AlgorithmParameters params = cipher.getParameters();
+
+ // Test DECRYPT_MODE
+ Key key = getDecryptionKey(algorithm, secretKeys, keyPairs);
+ cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, key, params);
+ assertSame(provider, cipher.getProvider());
+
+ // Test UNWRAP_MODE
+ cipher = Cipher.getInstance(algorithm);
+ if (params != null) {
+ cipher.init(Cipher.UNWRAP_MODE, key, params);
+ } else {
+ cipher.init(Cipher.UNWRAP_MODE, key);
+ }
+ assertSame(provider, cipher.getProvider());
+ } catch (Throwable e) {
+ throw new RuntimeException(algorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testAndroidKeyStorePublicKeysAcceptedByHighestPriorityProviderWhenEncrypting()
+ throws Exception {
+ Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ if (isSymmetric(algorithm)) {
+ continue;
+ }
+ try {
+ Key key = getEncryptionKey(algorithm, Collections.<SecretKey>emptyList(), keyPairs);
+
+ Cipher cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+
+ cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.WRAP_MODE, key);
+ } catch (Throwable e) {
+ throw new RuntimeException(algorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testCiphertextGeneratedByAndroidKeyStoreDecryptsByAndroidKeyStore()
+ throws Exception {
+ Collection<SecretKey> secretKeys = importDefaultKatSecretKeys();
+ Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ Key encryptionKey = getEncryptionKey(algorithm, secretKeys, keyPairs);
+ Cipher cipher = Cipher.getInstance(algorithm, provider);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ AlgorithmParameters params = cipher.getParameters();
+ byte[] expectedPlaintext = "Very secret message goes here...".getBytes("UTF-8");
+ byte[] ciphertext = cipher.doFinal(expectedPlaintext);
+ if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+ // RSA decryption without padding left-pads resulting plaintext with NUL bytes
+ // to the length of RSA modulus.
+ int modulusLengthBytes =
+ (((RSAKey) encryptionKey).getModulus().bitLength() + 7) / 8;
+ expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+ expectedPlaintext, modulusLengthBytes);
+ }
+
+ cipher = Cipher.getInstance(algorithm, provider);
+ Key decryptionKey = getDecryptionKey(algorithm, secretKeys, keyPairs);
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+ byte[] actualPlaintext = cipher.doFinal(ciphertext);
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+ } catch (Throwable e) {
+ throw new RuntimeException(algorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testCiphertextGeneratedByHighestPriorityProviderDecryptsByAndroidKeyStore()
+ throws Exception {
+ Collection<SecretKey> secretKeys = getDefaultKatSecretKeys();
+ Collection<SecretKey> keystoreSecretKeys = importDefaultKatSecretKeys();
+ Collection<KeyPair> keyPairs = getDefaultKatKeyPairs();
+ Collection<KeyPair> keystoreKeyPairs = importDefaultKatKeyPairs();
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ Key encryptionKey = getEncryptionKey(algorithm, secretKeys, keyPairs);
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ } catch (InvalidKeyException e) {
+ // No providers support encrypting using this algorithm and key.
+ continue;
+ }
+ if (provider == cipher.getProvider()) {
+ // This is covered by another test.
+ continue;
+ }
+ AlgorithmParameters params = cipher.getParameters();
+
+ // TODO: Remove this workaround for Bug 22405492 once the issue is fixed. The issue
+ // is that Bouncy Castle incorrectly defaults the MGF1 digest to the digest
+ // specified in the transformation. RI and Android Keystore keep the MGF1 digest
+ // defaulted at SHA-1.
+ if ((params != null) && ("OAEP".equalsIgnoreCase(params.getAlgorithm()))) {
+ OAEPParameterSpec spec = params.getParameterSpec(OAEPParameterSpec.class);
+ if (!"SHA-1".equalsIgnoreCase(
+ ((MGF1ParameterSpec) spec.getMGFParameters()).getDigestAlgorithm())) {
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new OAEPParameterSpec(
+ spec.getDigestAlgorithm(),
+ "MGF1",
+ MGF1ParameterSpec.SHA1,
+ PSource.PSpecified.DEFAULT));
+ params = cipher.getParameters();
+ }
+ }
+
+ byte[] expectedPlaintext = "Very secret message goes here...".getBytes("UTF-8");
+ byte[] ciphertext = cipher.doFinal(expectedPlaintext);
+ if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+ // RSA decryption without padding left-pads resulting plaintext with NUL bytes
+ // to the length of RSA modulus.
+ int modulusLengthBytes =
+ (((RSAKey) encryptionKey).getModulus().bitLength() + 7) / 8;
+ expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+ expectedPlaintext, modulusLengthBytes);
+ }
+
+ // TODO: Remove this workaround for Bug 22319986 once the issue is fixed. The issue
+ // is that Conscrypt and Bouncy Castle's AES/GCM/NoPadding implementations return
+ // AlgorithmParameters of algorithm "AES" from which it's impossible to obtain a
+ // GCMParameterSpec. They should be returning AlgorithmParameters of algorithm
+ // "GCM".
+ if (("AES/GCM/NoPadding".equalsIgnoreCase(algorithm))
+ && (!"GCM".equalsIgnoreCase(params.getAlgorithm()))) {
+ params = AlgorithmParameters.getInstance("GCM");
+ params.init(new GCMParameterSpec(128, cipher.getIV()));
+ }
+
+ cipher = Cipher.getInstance(algorithm, provider);
+ Key decryptionKey = getDecryptionKey(
+ algorithm, keystoreSecretKeys, keystoreKeyPairs);
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+ byte[] actualPlaintext = cipher.doFinal(ciphertext);
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+ } catch (Throwable e) {
+ throw new RuntimeException(algorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testCiphertextGeneratedByAndroidKeyStoreDecryptsByHighestPriorityProvider()
+ throws Exception {
+ Collection<SecretKey> secretKeys = getDefaultKatSecretKeys();
+ Collection<SecretKey> keystoreSecretKeys = importDefaultKatSecretKeys();
+ Collection<KeyPair> keyPairs = getDefaultKatKeyPairs();
+ Collection<KeyPair> keystoreKeyPairs = importDefaultKatKeyPairs();
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ Key encryptionKey =
+ getEncryptionKey(algorithm, keystoreSecretKeys, keystoreKeyPairs);
+ Cipher cipher = Cipher.getInstance(algorithm, provider);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ AlgorithmParameters params = cipher.getParameters();
+
+ byte[] expectedPlaintext = "Very secret message goes here...".getBytes("UTF-8");
+ byte[] ciphertext = cipher.doFinal(expectedPlaintext);
+ if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+ // RSA decryption without padding left-pads resulting plaintext with NUL bytes
+ // to the length of RSA modulus.
+ int modulusLengthBytes =
+ (((RSAKey) encryptionKey).getModulus().bitLength() + 7) / 8;
+ expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+ expectedPlaintext, modulusLengthBytes);
+ }
+
+ Key decryptionKey = getDecryptionKey(algorithm, secretKeys, keyPairs);
+ try {
+ cipher = Cipher.getInstance(algorithm);
+ if (params != null) {
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+ } else {
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey);
+ }
+ } catch (InvalidKeyException e) {
+ // No providers support decrypting using this algorithm and key.
+ continue;
+ }
+ if (provider == cipher.getProvider()) {
+ // This is covered by another test.
+ continue;
+ }
+ byte[] actualPlaintext = cipher.doFinal(ciphertext);
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+ } catch (Throwable e) {
+ throw new RuntimeException(algorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testKat() throws Exception {
+ Collection<SecretKey> secretKeys = importDefaultKatSecretKeys();
+ Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ Key key = getDecryptionKey(algorithm, secretKeys, keyPairs);
+ KatVector testVector = KAT_VECTORS.get(algorithm);
+ assertNotNull(testVector);
+ Cipher cipher = Cipher.getInstance(algorithm, provider);
+ cipher.init(Cipher.DECRYPT_MODE, key, testVector.params);
+ byte[] actualPlaintext = cipher.doFinal(testVector.ciphertext);
+ byte[] expectedPlaintext = testVector.plaintext;
+ if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+ // RSA decryption without padding left-pads resulting plaintext with NUL bytes
+ // to the length of RSA modulus.
+ int modulusLengthBytes =
+ (((RSAKey) key).getModulus().bitLength() + 7) / 8;
+ expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+ expectedPlaintext, modulusLengthBytes);
+ }
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+ if (!isRandomizedEncryption(algorithm)) {
+ // Deterministic encryption: ciphertext depends only on plaintext and input
+ // parameters. Assert that encrypting the plaintext results in the same
+ // ciphertext as in the test vector.
+ key = getEncryptionKey(algorithm, secretKeys, keyPairs);
+ cipher = Cipher.getInstance(algorithm, provider);
+ cipher.init(Cipher.ENCRYPT_MODE, key, testVector.params);
+ byte[] actualCiphertext = cipher.doFinal(testVector.plaintext);
+ MoreAsserts.assertEquals(testVector.ciphertext, actualCiphertext);
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ private static boolean isRandomizedEncryption(String transformation) {
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ return (transformationUpperCase.endsWith("/PKCS1PADDING"))
+ || (transformationUpperCase.contains("OAEP"));
+ }
+
+ public void testInitDecryptFailsWhenNotAuthorizedToDecrypt() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ try {
+ assertInitDecryptSucceeds(transformation, good.build());
+ assertInitDecryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good, KeyProperties.PURPOSE_ENCRYPT).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitEncryptSymmetricFailsWhenNotAuthorizedToEncrypt() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (!isSymmetric(transformation)) {
+ continue;
+ }
+
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good, KeyProperties.PURPOSE_DECRYPT).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitEncryptAsymmetricIgnoresAuthorizedPurposes() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (isSymmetric(transformation)) {
+ continue;
+ }
+
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptSucceeds(transformation,
+ TestUtils.buildUpon(good, 0).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitDecryptFailsWhenBlockModeNotAuthorized() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ if (transformationUpperCase.startsWith("RSA/")) {
+ // Block modes do not apply
+ continue;
+ }
+ String authorizedBlockMode =
+ (transformationUpperCase.contains("/CBC/")) ? "CTR" : "CBC";
+ try {
+ assertInitDecryptSucceeds(transformation, good.build());
+ assertInitDecryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good).setBlockModes(authorizedBlockMode).build());
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + transformation + " when authorized only for "
+ + authorizedBlockMode,
+ e);
+ }
+ }
+ }
+
+ public void testInitEncryptSymmetricFailsWhenBlockModeNotAuthorized() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (!isSymmetric(transformation)) {
+ continue;
+ }
+
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ if (transformationUpperCase.startsWith("RSA/")) {
+ // Block modes do not apply
+ continue;
+ }
+ String authorizedBlockMode =
+ (transformationUpperCase.contains("/CBC/")) ? "CTR" : "CBC";
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good).setBlockModes(authorizedBlockMode).build());
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + transformation + " when authorized only for "
+ + authorizedBlockMode,
+ e);
+ }
+ }
+ }
+
+ public void testInitEncryptAsymmetricIgnoresAuthorizedBlockModes() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (isSymmetric(transformation)) {
+ continue;
+ }
+
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptSucceeds(transformation,
+ TestUtils.buildUpon(good).setBlockModes().build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitDecryptFailsWhenDigestNotAuthorized() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ if ((transformationUpperCase.endsWith("/NOPADDING"))
+ || (transformationUpperCase.endsWith("/PKCS1PADDING"))
+ || (transformationUpperCase.endsWith("/PKCS7PADDING"))) {
+ // Digest not used
+ continue;
+ }
+ String authorizedDigest =
+ (transformationUpperCase.contains("SHA-256")) ? "SHA-512" : "SHA-256";
+ try {
+ assertInitDecryptSucceeds(transformation, good.build());
+ assertInitDecryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good).setDigests(authorizedDigest).build());
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + transformation + " when authorized only for "
+ + authorizedDigest,
+ e);
+ }
+ }
+ }
+
+ public void testInitEncryptSymmetricFailsWhenDigestNotAuthorized() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (!isSymmetric(transformation)) {
+ continue;
+ }
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ if ((transformationUpperCase.endsWith("/NOPADDING"))
+ || (transformationUpperCase.endsWith("/PKCS1PADDING"))
+ || (transformationUpperCase.endsWith("/PKCS7PADDING"))) {
+ // Digest not used
+ continue;
+ }
+ String authorizedDigest =
+ (transformationUpperCase.contains("SHA-256")) ? "SHA-512" : "SHA-256";
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good).setDigests(authorizedDigest).build());
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + transformation + " when authorized only for "
+ + authorizedDigest,
+ e);
+ }
+ }
+ }
+
+ public void testInitEncryptAsymmetricIgnoresAuthorizedDigests() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (isSymmetric(transformation)) {
+ continue;
+ }
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptSucceeds(transformation,
+ TestUtils.buildUpon(good).setDigests().build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitDecryptFailsWhenPaddingSchemeNotAuthorized() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ String authorizedPaddingScheme;
+ if (transformationUpperCase.startsWith("RSA/")) {
+ authorizedPaddingScheme = transformationUpperCase.contains("PKCS1PADDING")
+ ? "OAEPPadding" : "PKCS1Padding";
+ } else {
+ authorizedPaddingScheme = transformationUpperCase.contains("PKCS1PADDING")
+ ? "NoPadding" : "PKCS1Padding";
+ }
+ try {
+ assertInitDecryptSucceeds(transformation, good.build());
+ assertInitDecryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good)
+ .setEncryptionPaddings(authorizedPaddingScheme)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + transformation + " when authorized only for "
+ + authorizedPaddingScheme,
+ e);
+ }
+ }
+ }
+
+ public void testInitEncryptSymmetricFailsWhenPaddingSchemeNotAuthorized() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (!isSymmetric(transformation)) {
+ continue;
+ }
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ String authorizedPaddingScheme;
+ if (transformationUpperCase.startsWith("RSA/")) {
+ authorizedPaddingScheme = transformationUpperCase.contains("PKCS1PADDING")
+ ? "OAEPPadding" : "PKCS1Padding";
+ } else {
+ authorizedPaddingScheme = transformationUpperCase.contains("PKCS1PADDING")
+ ? "NoPadding" : "PKCS1Padding";
+ }
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good)
+ .setEncryptionPaddings(authorizedPaddingScheme)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + transformation + " when authorized only for "
+ + authorizedPaddingScheme,
+ e);
+ }
+ }
+ }
+
+ public void testInitEncryptAsymmetricIgnoresAuthorizedPaddingSchemes() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (isSymmetric(transformation)) {
+ continue;
+ }
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptSucceeds(transformation,
+ TestUtils.buildUpon(good)
+ .setEncryptionPaddings()
+ .setSignaturePaddings()
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitDecryptFailsWhenKeyNotYetValid() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ try {
+ assertInitDecryptSucceeds(transformation, good.build());
+ assertInitDecryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitEncryptSymmetricFailsWhenKeyNotYetValid() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (!isSymmetric(transformation)) {
+ continue;
+ }
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitEncryptAsymmetricIgnoresThatKeyNotYetValid() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (isSymmetric(transformation)) {
+ continue;
+ }
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptSucceeds(transformation,
+ TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitDecryptFailsWhenKeyNoLongerValidForConsumption() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ try {
+ assertInitDecryptSucceeds(transformation, good.build());
+ assertInitDecryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForConsumptionEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitDecryptIgnoresThatKeyNoLongerValidForOrigination() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ try {
+ assertInitDecryptSucceeds(transformation, good.build());
+ assertInitDecryptSucceeds(transformation,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForOriginationEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitEncryptSymmetricFailsWhenKeyNoLongerValidForOrigination() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (!isSymmetric(transformation)) {
+ continue;
+ }
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptThrowsInvalidKeyException(transformation,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForOriginationEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitEncryptSymmetricIgnoresThatKeyNoLongerValidForConsumption()
+ throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (!isSymmetric(transformation)) {
+ continue;
+ }
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptSucceeds(transformation,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForConsumptionEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ public void testInitEncryptAsymmetricIgnoresThatKeyNoLongerValid() throws Exception {
+ KeyProtection.Builder good = GOOD_IMPORT_PARAMS_BUILDER;
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ if (isSymmetric(transformation)) {
+ continue;
+ }
+ try {
+ assertInitEncryptSucceeds(transformation, good.build());
+ assertInitEncryptSucceeds(transformation,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForOriginationEnd(badEndDate)
+ .build());
+ assertInitEncryptSucceeds(transformation,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForConsumptionEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + transformation, e);
+ }
+ }
+ }
+
+ private AlgorithmParameterSpec getWorkingDecryptionParameterSpec(String transformation) {
+ String transformationUpperCase = transformation.toUpperCase();
+ if (transformationUpperCase.startsWith("RSA/")) {
+ return null;
+ } else if (transformationUpperCase.startsWith("AES/")) {
+ if (transformationUpperCase.startsWith("AES/ECB")) {
+ return null;
+ } else if ((transformationUpperCase.startsWith("AES/CBC"))
+ || (transformationUpperCase.startsWith("AES/CTR"))) {
+ return new IvParameterSpec(
+ new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
+ } else if (transformationUpperCase.startsWith("AES/GCM")) {
+ return new GCMParameterSpec(
+ 128,
+ new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
+ }
+ }
+
+ throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ }
+
+ private void assertInitDecryptSucceeds(String transformation, KeyProtection importParams)
+ throws Exception {
+ Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
+ Key key = importDefaultKatDecryptionKey(transformation, importParams);
+ AlgorithmParameterSpec params = getWorkingDecryptionParameterSpec(transformation);
+ cipher.init(Cipher.DECRYPT_MODE, key, params);
+ }
+
+ private void assertInitDecryptThrowsInvalidKeyException(
+ String transformation, KeyProtection importParams) throws Exception {
+ Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
+ Key key = importDefaultKatDecryptionKey(transformation, importParams);
+ AlgorithmParameterSpec params = getWorkingDecryptionParameterSpec(transformation);
+ try {
+ cipher.init(Cipher.DECRYPT_MODE, key, params);
+ fail("InvalidKeyException should have been thrown");
+ } catch (InvalidKeyException expected) {}
+ }
+
+ private void assertInitEncryptSucceeds(String transformation, KeyProtection importParams)
+ throws Exception {
+ Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
+ Key key = importDefaultKatEncryptionKey(transformation, importParams);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ }
+
+ private void assertInitEncryptThrowsInvalidKeyException(
+ String transformation, KeyProtection importParams) throws Exception {
+ Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
+ Key key = importDefaultKatEncryptionKey(transformation, importParams);
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ fail("InvalidKeyException should have been thrown");
+ } catch (InvalidKeyException expected) {}
+ }
+
+ private Key importDefaultKatEncryptionKey(String transformation,
+ KeyProtection importParams) throws Exception {
+ String transformationUpperCase = transformation.toUpperCase();
+ if (transformationUpperCase.startsWith("RSA/")) {
+ return TestUtils.importIntoAndroidKeyStore("testRsa",
+ TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key2_pkcs8),
+ TestUtils.getRawResX509Certificate(getContext(), R.raw.rsa_key2_cert),
+ importParams).getPublic();
+ } else if (transformationUpperCase.startsWith("AES/")) {
+ return TestUtils.importIntoAndroidKeyStore("testAes",
+ new SecretKeySpec(AES_KAT_KEY_BYTES, "AES"),
+ importParams);
+ } else {
+ throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ }
+ }
+
+ private Key importDefaultKatDecryptionKey(String transformation,
+ KeyProtection importParams) throws Exception {
+ String transformationUpperCase = transformation.toUpperCase();
+ if (transformationUpperCase.startsWith("RSA/")) {
+ return TestUtils.importIntoAndroidKeyStore("testRsa",
+ TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key2_pkcs8),
+ TestUtils.getRawResX509Certificate(getContext(), R.raw.rsa_key2_cert),
+ importParams).getPrivate();
+ } else if (transformationUpperCase.startsWith("AES/")) {
+ return TestUtils.importIntoAndroidKeyStore("testAes",
+ new SecretKeySpec(AES_KAT_KEY_BYTES, "AES"),
+ importParams);
+ } else {
+ throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ }
+ }
+
+ private static Key getEncryptionKey(String transformation,
+ Iterable<SecretKey> secretKeys,
+ Iterable<KeyPair> keyPairs) {
+ String transformationUpperCase = transformation.toUpperCase();
+ if (transformationUpperCase.startsWith("RSA/")) {
+ return TestUtils.getKeyPairForKeyAlgorithm("RSA", keyPairs).getPublic();
+ } else if (transformationUpperCase.startsWith("AES/")) {
+ return TestUtils.getKeyForKeyAlgorithm("AES", secretKeys);
+ } else {
+ throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ }
+ }
+
+ private static Key getDecryptionKey(String transformation,
+ Iterable<SecretKey> secretKeys,
+ Iterable<KeyPair> keyPairs) {
+ String transformationUpperCase = transformation.toUpperCase();
+ if (transformationUpperCase.startsWith("RSA/")) {
+ return TestUtils.getKeyPairForKeyAlgorithm("RSA", keyPairs).getPrivate();
+ } else if (transformationUpperCase.startsWith("AES/")) {
+ return TestUtils.getKeyForKeyAlgorithm("AES", secretKeys);
+ } else {
+ throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ }
+ }
+
+ private Collection<KeyPair> getDefaultKatKeyPairs() throws Exception {
+ return Arrays.asList(
+ new KeyPair(
+ TestUtils.getRawResX509Certificate(
+ getContext(), R.raw.rsa_key2_cert).getPublicKey(),
+ TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key2_pkcs8)));
+ }
+
+ private Collection<KeyPair> importDefaultKatKeyPairs() throws Exception {
+ return Arrays.asList(
+ TestUtils.importIntoAndroidKeyStore(
+ "testRsa",
+ TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key2_pkcs8),
+ TestUtils.getRawResX509Certificate(getContext(), R.raw.rsa_key2_cert),
+ new KeyProtection.Builder(
+ KeyProperties.PURPOSE_DECRYPT)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setRandomizedEncryptionRequired(false) // due to PADDING_NONE
+ .setDigests(KeyProperties.DIGEST_NONE) // due to OAEP
+ .build()));
+ }
+
+ private Collection<SecretKey> getDefaultKatSecretKeys() throws Exception {
+ return Arrays.asList((SecretKey) new SecretKeySpec(AES_KAT_KEY_BYTES, "AES"));
+ }
+
+ private Collection<SecretKey> importDefaultKatSecretKeys() throws Exception {
+ return Arrays.asList(
+ TestUtils.importIntoAndroidKeyStore("testAes",
+ new SecretKeySpec(AES_KAT_KEY_BYTES, "AES"),
+ new KeyProtection.Builder(
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_ECB,
+ KeyProperties.BLOCK_MODE_CBC,
+ KeyProperties.BLOCK_MODE_CTR,
+ KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setRandomizedEncryptionRequired(false) // due to ECB
+ .build()));
+ }
+
+ private static boolean isSymmetric(String transformation) {
+ return transformation.toUpperCase(Locale.US).startsWith("AES/");
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java
index 2330209..271b40b 100644
--- a/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java
@@ -109,10 +109,7 @@
assertTrue(signature.verify(sigBytes));
// Assert that the message is left-padded with zero bits
- byte[] fullLengthMessage = new byte[keySizeBits / 8];
- System.arraycopy(message, 0,
- fullLengthMessage, fullLengthMessage.length - message.length,
- message.length);
+ byte[] fullLengthMessage = TestUtils.leftPadWithZeroBytes(message, keySizeBits / 8);
signature.update(fullLengthMessage);
assertTrue(signature.verify(sigBytes));
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
new file mode 100644
index 0000000..b51d300
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyInfo;
+import android.security.keystore.KeyProperties;
+import android.test.MoreAsserts;
+
+import junit.framework.TestCase;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+import java.security.Provider.Service;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+public class KeyGeneratorTest extends TestCase {
+ private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME;
+
+ private static final String[] EXPECTED_ALGORITHMS = {
+ "AES",
+ "HmacSHA1",
+ "HmacSHA224",
+ "HmacSHA256",
+ "HmacSHA384",
+ "HmacSHA512",
+ };
+
+ private static final Map<String, Integer> DEFAULT_KEY_SIZES =
+ new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ static {
+ DEFAULT_KEY_SIZES.put("AES", 128);
+ DEFAULT_KEY_SIZES.put("HmacSHA1", 160);
+ DEFAULT_KEY_SIZES.put("HmacSHA224", 224);
+ DEFAULT_KEY_SIZES.put("HmacSHA256", 256);
+ DEFAULT_KEY_SIZES.put("HmacSHA384", 384);
+ DEFAULT_KEY_SIZES.put("HmacSHA512", 512);
+ }
+
+ private static final int[] AES_SUPPORTED_KEY_SIZES = new int[] {128, 192, 256};
+
+ public void testAlgorithmList() {
+ // Assert that Android Keystore Provider exposes exactly the expected KeyGenerator
+ // algorithms. We don't care whether the algorithms are exposed via aliases, as long as
+ // canonical names of algorithms are accepted. If the Provider exposes extraneous
+ // algorithms, it'll be caught because it'll have to expose at least one Service for such an
+ // algorithm, and this Service's algorithm will not be in the expected set.
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ Set<Service> services = provider.getServices();
+ Set<String> actualAlgsLowerCase = new HashSet<String>();
+ Set<String> expectedAlgsLowerCase = new HashSet<String>(
+ Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS)));
+ for (Service service : services) {
+ if ("KeyGenerator".equalsIgnoreCase(service.getType())) {
+ String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
+ actualAlgsLowerCase.add(algLowerCase);
+ }
+ }
+
+ TestUtils.assertContentsInAnyOrder(actualAlgsLowerCase,
+ expectedAlgsLowerCase.toArray(new String[0]));
+ }
+
+ public void testGenerateWithoutInitThrowsIllegalStateException() throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ try {
+ keyGenerator.generateKey();
+ fail();
+ } catch (IllegalStateException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithKeySizeThrowsUnsupportedOperationException() throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm);
+ try {
+ keyGenerator.init(keySizeBits);
+ fail();
+ } catch (UnsupportedOperationException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithKeySizeAndSecureRandomThrowsUnsupportedOperationException()
+ throws Exception {
+ SecureRandom rng = new SecureRandom();
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm);
+ try {
+ keyGenerator.init(keySizeBits, rng);
+ fail();
+ } catch (UnsupportedOperationException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithNullAlgParamsThrowsInvalidAlgorithmParameterException()
+ throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ try {
+ keyGenerator.init((AlgorithmParameterSpec) null);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithNullAlgParamsAndSecureRandomThrowsInvalidAlgorithmParameterException()
+ throws Exception {
+ SecureRandom rng = new SecureRandom();
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ try {
+ keyGenerator.init((AlgorithmParameterSpec) null, rng);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithAlgParamsAndNullSecureRandom()
+ throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ keyGenerator.init(getWorkingSpec().build(), (SecureRandom) null);
+ // Check that generateKey doesn't fail either, just in case null SecureRandom
+ // causes trouble there.
+ keyGenerator.generateKey();
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithUnsupportedAlgParamsTypeThrowsInvalidAlgorithmParameterException()
+ throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ try {
+ keyGenerator.init(new ECGenParameterSpec("secp256r1"));
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testDefaultKeySize() throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm);
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ keyGenerator.init(getWorkingSpec().build());
+ SecretKey key = keyGenerator.generateKey();
+ assertEquals(expectedSizeBits, TestUtils.getKeyInfo(key).getKeySize());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testAesKeySupportedSizes() throws Exception {
+ KeyGenerator keyGenerator = getKeyGenerator("AES");
+ KeyGenParameterSpec.Builder goodSpec = getWorkingSpec();
+ CountingSecureRandom rng = new CountingSecureRandom();
+ for (int i = -16; i <= 512; i++) {
+ try {
+ rng.resetCounters();
+ KeyGenParameterSpec spec;
+ if (i >= 0) {
+ spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build();
+ } else {
+ try {
+ spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ continue;
+ }
+ }
+ rng.resetCounters();
+ if (TestUtils.contains(AES_SUPPORTED_KEY_SIZES, i)) {
+ keyGenerator.init(spec, rng);
+ SecretKey key = keyGenerator.generateKey();
+ assertEquals(i, TestUtils.getKeyInfo(key).getKeySize());
+ assertEquals((i + 7) / 8, rng.getOutputSizeBytes());
+ } else {
+ try {
+ keyGenerator.init(spec, rng);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ assertEquals(0, rng.getOutputSizeBytes());
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed to key size " + i, e);
+ }
+ }
+ }
+
+ public void testHmacKeySupportedSizes() throws Exception {
+ CountingSecureRandom rng = new CountingSecureRandom();
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ if (!TestUtils.isHmacAlgorithm(algorithm)) {
+ continue;
+ }
+
+ for (int i = -16; i <= 1024; i++) {
+ try {
+ rng.resetCounters();
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ KeyGenParameterSpec spec;
+ if (i >= 0) {
+ spec = getWorkingSpec().setKeySize(i).build();
+ } else {
+ try {
+ spec = getWorkingSpec().setKeySize(i).build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ continue;
+ }
+ }
+ if ((i > 0) && ((i % 8 ) == 0)) {
+ keyGenerator.init(spec, rng);
+ SecretKey key = keyGenerator.generateKey();
+ assertEquals(i, TestUtils.getKeyInfo(key).getKeySize());
+ assertEquals((i + 7) / 8, rng.getOutputSizeBytes());
+ } else {
+ try {
+ keyGenerator.init(spec, rng);
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ assertEquals(0, rng.getOutputSizeBytes());
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + algorithm + " with key size " + i, e);
+ }
+ }
+ }
+ }
+
+ public void testInitWithUnknownBlockModeFails() {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ try {
+ keyGenerator.init(getWorkingSpec().setBlockModes("weird").build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithUnknownEncryptionPaddingFails() {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ try {
+ keyGenerator.init(getWorkingSpec().setEncryptionPaddings("weird").build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithSignaturePaddingFails() {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ try {
+ keyGenerator.init(getWorkingSpec()
+ .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+ .build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithUnknownDigestFails() {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ try {
+ String[] digests;
+ if (TestUtils.isHmacAlgorithm(algorithm)) {
+ // The digest from HMAC key algorithm must be specified in the list of
+ // authorized digests (if the list if provided).
+ digests = new String[] {algorithm, "weird"};
+ } else {
+ digests = new String[] {"weird"};
+ }
+ keyGenerator.init(getWorkingSpec().setDigests(digests).build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFails() {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ if (!TestUtils.isHmacAlgorithm(algorithm)) {
+ continue;
+ }
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+
+ // Authorized for digest(s) none of which is the one implied by key algorithm.
+ try {
+ String digest = TestUtils.getHmacAlgorithmDigest(algorithm);
+ String anotherDigest = KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest)
+ ? KeyProperties.DIGEST_SHA512 : KeyProperties.DIGEST_SHA256;
+ keyGenerator.init(getWorkingSpec().setDigests(anotherDigest).build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+
+ // Authorized for empty set of digests
+ try {
+ keyGenerator.init(getWorkingSpec().setDigests().build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitRandomizedEncryptionRequiredButViolatedFails() throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ try {
+ keyGenerator.init(getWorkingSpec(
+ KeyProperties.PURPOSE_ENCRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
+ .build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testGenerateHonorsAuthorizations() throws Exception {
+ Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS);
+ Date keyValidityForOriginationEnd =
+ new Date(System.currentTimeMillis() + TestUtils.DAY_IN_MILLIS);
+ Date keyValidityForConsumptionEnd =
+ new Date(System.currentTimeMillis() + 3 * TestUtils.DAY_IN_MILLIS);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ String[] blockModes =
+ new String[] {KeyProperties.BLOCK_MODE_GCM, KeyProperties.BLOCK_MODE_CBC};
+ String[] encryptionPaddings =
+ new String[] {KeyProperties.ENCRYPTION_PADDING_PKCS7,
+ KeyProperties.ENCRYPTION_PADDING_NONE};
+ String[] digests;
+ int purposes;
+ if (TestUtils.isHmacAlgorithm(algorithm)) {
+ String digest = TestUtils.getHmacAlgorithmDigest(algorithm);
+ String anotherDigest = KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest)
+ ? KeyProperties.DIGEST_SHA512 : KeyProperties.DIGEST_SHA256;
+ digests = new String[] {anotherDigest, digest};
+ purposes = KeyProperties.PURPOSE_SIGN;
+ } else {
+ digests = new String[] {KeyProperties.DIGEST_SHA384};
+ purposes = KeyProperties.PURPOSE_DECRYPT;
+ }
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ keyGenerator.init(getWorkingSpec(purposes)
+ .setBlockModes(blockModes)
+ .setEncryptionPaddings(encryptionPaddings)
+ .setDigests(digests)
+ .setKeyValidityStart(keyValidityStart)
+ .setKeyValidityForOriginationEnd(keyValidityForOriginationEnd)
+ .setKeyValidityForConsumptionEnd(keyValidityForConsumptionEnd)
+ .build());
+ SecretKey key = keyGenerator.generateKey();
+ assertEquals(algorithm, key.getAlgorithm());
+
+ KeyInfo keyInfo = TestUtils.getKeyInfo(key);
+ assertEquals(purposes, keyInfo.getPurposes());
+ TestUtils.assertContentsInAnyOrder(
+ Arrays.asList(blockModes), keyInfo.getBlockModes());
+ TestUtils.assertContentsInAnyOrder(
+ Arrays.asList(encryptionPaddings), keyInfo.getEncryptionPaddings());
+ TestUtils.assertContentsInAnyOrder(Arrays.asList(digests), keyInfo.getDigests());
+ MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getSignaturePaddings()));
+ assertEquals(keyValidityStart, keyInfo.getKeyValidityStart());
+ assertEquals(keyValidityForOriginationEnd,
+ keyInfo.getKeyValidityForOriginationEnd());
+ assertEquals(keyValidityForConsumptionEnd,
+ keyInfo.getKeyValidityForConsumptionEnd());
+ assertFalse(keyInfo.isUserAuthenticationRequired());
+ assertFalse(keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ private static KeyGenParameterSpec.Builder getWorkingSpec() {
+ return getWorkingSpec(0);
+ }
+
+ private static KeyGenParameterSpec.Builder getWorkingSpec(int purposes) {
+ return new KeyGenParameterSpec.Builder("test1", purposes);
+ }
+
+ private static KeyGenerator getKeyGenerator(String algorithm) throws NoSuchAlgorithmException,
+ NoSuchProviderException {
+ return KeyGenerator.getInstance(algorithm, EXPECTED_PROVIDER_NAME);
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
index e3540b4..3d3c909 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
@@ -34,7 +34,10 @@
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PrivateKey;
+import java.security.Provider;
import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Provider.Service;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECKey;
@@ -48,7 +51,12 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -99,6 +107,20 @@
private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970
private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
+ private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME;
+
+ private static final String[] EXPECTED_ALGORITHMS = {
+ "EC",
+ "RSA",
+ };
+
+ private static final Map<String, Integer> DEFAULT_KEY_SIZES =
+ new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ static {
+ DEFAULT_KEY_SIZES.put("EC", 256);
+ DEFAULT_KEY_SIZES.put("RSA", 2048);
+ }
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -107,6 +129,29 @@
mKeyStore.load(null, null);
}
+ public void testAlgorithmList() {
+ // Assert that Android Keystore Provider exposes exactly the expected KeyPairGenerator
+ // algorithms. We don't care whether the algorithms are exposed via aliases, as long as
+ // canonical names of algorithms are accepted. If the Provider exposes extraneous
+ // algorithms, it'll be caught because it'll have to expose at least one Service for such an
+ // algorithm, and this Service's algorithm will not be in the expected set.
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ Set<Service> services = provider.getServices();
+ Set<String> actualAlgsLowerCase = new HashSet<String>();
+ Set<String> expectedAlgsLowerCase = new HashSet<String>(
+ Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS)));
+ for (Service service : services) {
+ if ("KeyPairGenerator".equalsIgnoreCase(service.getType())) {
+ String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
+ actualAlgsLowerCase.add(algLowerCase);
+ }
+ }
+
+ TestUtils.assertContentsInAnyOrder(actualAlgsLowerCase,
+ expectedAlgsLowerCase.toArray(new String[0]));
+ }
+
public void testInitialize_LegacySpec() throws Exception {
@SuppressWarnings("deprecation")
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext())
@@ -164,6 +209,143 @@
}
}
+ public void testDefaultKeySize() throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm);
+ KeyPairGenerator generator = getGenerator(algorithm);
+ generator.initialize(getWorkingSpec().build());
+ KeyPair keyPair = generator.generateKeyPair();
+ assertEquals(expectedSizeBits,
+ TestUtils.getKeyInfo(keyPair.getPrivate()).getKeySize());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithUnknownBlockModeFails() {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyPairGenerator generator = getGenerator(algorithm);
+ try {
+ generator.initialize(getWorkingSpec().setBlockModes("weird").build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithUnknownEncryptionPaddingFails() {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyPairGenerator generator = getGenerator(algorithm);
+ try {
+ generator.initialize(getWorkingSpec().setEncryptionPaddings("weird").build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithUnknownSignaturePaddingFails() {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyPairGenerator generator = getGenerator(algorithm);
+ try {
+ generator.initialize(getWorkingSpec().setSignaturePaddings("weird").build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitWithUnknownDigestFails() {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyPairGenerator generator = getGenerator(algorithm);
+ try {
+ generator.initialize(getWorkingSpec().setDigests("weird").build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitRandomizedEncryptionRequiredButViolatedFails() throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyPairGenerator generator = getGenerator(algorithm);
+ try {
+ generator.initialize(getWorkingSpec(
+ KeyProperties.PURPOSE_ENCRYPT)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testGenerateHonorsAuthorizations() throws Exception {
+ Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS);
+ Date keyValidityForOriginationEnd =
+ new Date(System.currentTimeMillis() + TestUtils.DAY_IN_MILLIS);
+ Date keyValidityForConsumptionEnd =
+ new Date(System.currentTimeMillis() + 3 * TestUtils.DAY_IN_MILLIS);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ String[] blockModes =
+ new String[] {KeyProperties.BLOCK_MODE_GCM, KeyProperties.BLOCK_MODE_CBC};
+ String[] encryptionPaddings =
+ new String[] {KeyProperties.ENCRYPTION_PADDING_RSA_OAEP,
+ KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1};
+ String[] digests =
+ new String[] {KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1};
+ int purposes = KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_ENCRYPT;
+ KeyPairGenerator generator = getGenerator(algorithm);
+ generator.initialize(getWorkingSpec(purposes)
+ .setBlockModes(blockModes)
+ .setEncryptionPaddings(encryptionPaddings)
+ .setDigests(digests)
+ .setKeyValidityStart(keyValidityStart)
+ .setKeyValidityForOriginationEnd(keyValidityForOriginationEnd)
+ .setKeyValidityForConsumptionEnd(keyValidityForConsumptionEnd)
+ .build());
+ KeyPair keyPair = generator.generateKeyPair();
+ assertEquals(algorithm, keyPair.getPrivate().getAlgorithm());
+
+ KeyInfo keyInfo = TestUtils.getKeyInfo(keyPair.getPrivate());
+ assertEquals(purposes, keyInfo.getPurposes());
+ TestUtils.assertContentsInAnyOrder(
+ Arrays.asList(blockModes), keyInfo.getBlockModes());
+ TestUtils.assertContentsInAnyOrder(
+ Arrays.asList(encryptionPaddings), keyInfo.getEncryptionPaddings());
+ TestUtils.assertContentsInAnyOrder(Arrays.asList(digests), keyInfo.getDigests());
+ MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getSignaturePaddings()));
+ assertEquals(keyValidityStart, keyInfo.getKeyValidityStart());
+ assertEquals(keyValidityForOriginationEnd,
+ keyInfo.getKeyValidityForOriginationEnd());
+ assertEquals(keyValidityForConsumptionEnd,
+ keyInfo.getKeyValidityForConsumptionEnd());
+ assertFalse(keyInfo.isUserAuthenticationRequired());
+ assertFalse(keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
@SuppressWarnings("deprecation")
public void testGenerate_EC_LegacySpec() throws Exception {
// There are three legacy ways to generate an EC key pair using Android Keystore
@@ -1258,4 +1440,12 @@
+ "Expected one of " + Arrays.asList(expected)
+ ", actual: <" + actual + ">");
}
+
+ private KeyGenParameterSpec.Builder getWorkingSpec() {
+ return getWorkingSpec(0);
+ }
+
+ private KeyGenParameterSpec.Builder getWorkingSpec(int purposes) {
+ return new KeyGenParameterSpec.Builder(TEST_ALIAS_1, purposes);
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/MacTest.java b/tests/tests/keystore/src/android/keystore/cts/MacTest.java
index d654e3a..8dcf47e 100644
--- a/tests/tests/keystore/src/android/keystore/cts/MacTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/MacTest.java
@@ -37,6 +37,9 @@
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
+/**
+ * Tests for algorithm-agnostic functionality of MAC implementations backed by Android Keystore.
+ */
public class MacTest extends TestCase {
private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
@@ -95,7 +98,7 @@
}
- private static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
+ private static final long DAY_IN_MILLIS = TestUtils.DAY_IN_MILLIS;
public void testAlgorithmList() {
// Assert that Android Keystore Provider exposes exactly the expected MAC algorithms. We
@@ -131,13 +134,13 @@
Mac mac = Mac.getInstance(algorithm);
mac.init(key);
assertSame(provider, mac.getProvider());
- } catch (Exception e) {
+ } catch (Throwable e) {
throw new RuntimeException(algorithm + " failed", e);
}
}
}
- public void testGeneratedSignatureVerifies() throws Exception {
+ public void testMacGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore() throws Exception {
SecretKey key = importDefaultKatKey();
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -145,18 +148,66 @@
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
// Generate a MAC
- Mac mac = Mac.getInstance(algorithm);
+ Mac mac = Mac.getInstance(algorithm, provider);
mac.init(key);
byte[] message = "This is a test".getBytes("UTF-8");
byte[] macBytes = mac.doFinal(message);
- assertMacVerifiesOneShot(algorithm, key, message, macBytes);
- } catch (Exception e) {
+ assertMacVerifiesOneShot(algorithm, provider, key, message, macBytes);
+ } catch (Throwable e) {
throw new RuntimeException(algorithm + " failed", e);
}
}
}
+ public void testMacGeneratedByAndroidKeyStoreVerifiesByHighestPriorityProvider()
+ throws Exception {
+ SecretKey key = getDefaultKatKey();
+ SecretKey keystoreKey = importDefaultKatKey();
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ // Generate a MAC
+ Mac mac = Mac.getInstance(algorithm, provider);
+ mac.init(keystoreKey);
+ byte[] message = "This is a test".getBytes("UTF-8");
+ byte[] macBytes = mac.doFinal(message);
+
+ assertMacVerifiesOneShot(algorithm, key, message, macBytes);
+ } catch (Throwable e) {
+ throw new RuntimeException(algorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testMacGeneratedByHighestPriorityProviderVerifiesByAndroidKeyStore()
+ throws Exception {
+ SecretKey key = getDefaultKatKey();
+ SecretKey keystoreKey = importDefaultKatKey();
+
+ Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(keystoreProvider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ Provider signingProvider = null;
+ try {
+ // Generate a MAC
+ Mac mac = Mac.getInstance(algorithm);
+ mac.init(key);
+ signingProvider = mac.getProvider();
+ byte[] message = "This is a test".getBytes("UTF-8");
+ byte[] macBytes = mac.doFinal(message);
+
+ assertMacVerifiesOneShot(
+ algorithm, keystoreProvider, keystoreKey, message, macBytes);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ algorithm + " failed, signing provider: " + signingProvider, e);
+ }
+ }
+ }
+
public void testSmallMsgKat() throws Exception {
SecretKey key = importDefaultKatKey();
byte[] message = SHORT_MSG_KAT_MESSAGE;
@@ -202,44 +253,88 @@
}
public void testInitFailsWhenNotAuthorizedToSign() throws Exception {
- KeyProtection.Builder good = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN);
int badPurposes = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
| KeyProperties.PURPOSE_VERIFY;
- String algorithm = "HmacSHA512";
- assertInitSucceeds(algorithm, algorithm, good.build());
- assertInitThrowsInvalidKeyException(algorithm, algorithm,
- TestUtils.buildUpon(good, badPurposes).build());
+
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParams(algorithm);
+ assertInitSucceeds(algorithm, good.build());
+ assertInitThrowsInvalidKeyException(algorithm,
+ TestUtils.buildUpon(good, badPurposes).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
public void testInitFailsWhenDigestNotAuthorized() throws Exception {
- KeyProtection spec = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build();
- assertInitSucceeds("HmacSHA384", "HmacSHA384", spec);
- assertInitThrowsInvalidKeyException("HmacSHA256", "HmacSHA384", spec);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParams(algorithm);
+ assertInitSucceeds(algorithm, good.build());
+
+ String badKeyAlgorithm = ("HmacSHA256".equalsIgnoreCase(algorithm))
+ ? "HmacSHA384" : "HmacSHA256";
+ assertInitThrowsInvalidKeyException(algorithm, badKeyAlgorithm, good.build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
public void testInitFailsWhenKeyNotYetValid() throws Exception {
- KeyProtection.Builder good = new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setKeyValidityStart(new Date(System.currentTimeMillis() - DAY_IN_MILLIS));
- String algorithm = "HmacSHA224";
- assertInitSucceeds(algorithm, algorithm, good.build());
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParams(algorithm)
+ .setKeyValidityStart(new Date(System.currentTimeMillis() - DAY_IN_MILLIS));
+ assertInitSucceeds(algorithm, good.build());
- Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
- assertInitThrowsInvalidKeyException(algorithm, algorithm,
- TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
+ Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
+ assertInitThrowsInvalidKeyException(algorithm,
+ TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
- public void testInitFailsWhenKeyNoLongerValid() throws Exception {
- KeyProtection.Builder good = new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setKeyValidityForOriginationEnd(
- new Date(System.currentTimeMillis() + DAY_IN_MILLIS));
- String algorithm = "HmacSHA1";
- assertInitSucceeds(algorithm, algorithm, good.build());
+ public void testInitFailsWhenKeyNoLongerValidForOrigination() throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParams(algorithm)
+ .setKeyValidityForOriginationEnd(
+ new Date(System.currentTimeMillis() + DAY_IN_MILLIS));
+ assertInitSucceeds(algorithm, good.build());
- Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
- assertInitThrowsInvalidKeyException(algorithm, algorithm,
- TestUtils.buildUpon(good).setKeyValidityForOriginationEnd(badEndDate).build());
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ assertInitThrowsInvalidKeyException(algorithm,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForOriginationEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitIgnoresThatKeyNoLongerValidForConsumption() throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParams(algorithm)
+ .setKeyValidityForConsumptionEnd(
+ new Date(System.currentTimeMillis() + DAY_IN_MILLIS));
+ assertInitSucceeds(algorithm, good.build());
+
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ assertInitSucceeds(algorithm,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForConsumptionEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
private void assertMacVerifiesOneShot(
@@ -247,7 +342,17 @@
SecretKey key,
byte[] message,
byte[] mac) throws Exception {
- Mac m = Mac.getInstance(algorithm);
+ assertMacVerifiesOneShot(algorithm, null, key, message, mac);
+ }
+
+ private void assertMacVerifiesOneShot(
+ String algorithm,
+ Provider provider,
+ SecretKey key,
+ byte[] message,
+ byte[] mac) throws Exception {
+ Mac m = (provider != null)
+ ? Mac.getInstance(algorithm, provider) : Mac.getInstance(algorithm);
m.init(key);
byte[] mac2 = m.doFinal(message);
if (!Arrays.equals(mac, mac2)) {
@@ -273,7 +378,6 @@
}
}
-
private void assertMacVerifiesFedOneByteAtATime(
String algorithm,
SecretKey key,
@@ -318,6 +422,11 @@
}
}
+ private void assertInitSucceeds(String algorithm, KeyProtection keyProtection)
+ throws Exception {
+ assertInitSucceeds(algorithm, algorithm, keyProtection);
+ }
+
private void assertInitSucceeds(
String macAlgorithm, String keyAlgorithm, KeyProtection keyProtection)
throws Exception {
@@ -326,30 +435,33 @@
mac.init(key);
}
+ private void assertInitThrowsInvalidKeyException(String algorithm, KeyProtection keyProtection)
+ throws Exception {
+ assertInitThrowsInvalidKeyException(algorithm, algorithm, keyProtection);
+ }
+
private void assertInitThrowsInvalidKeyException(
String macAlgorithm, String keyAlgorithm, KeyProtection keyProtection)
throws Exception {
- assertInitThrowsInvalidKeyException(null, macAlgorithm, keyAlgorithm, keyProtection);
- }
-
-
- private void assertInitThrowsInvalidKeyException(
- String message, String macAlgorithm, String keyAlgorithm,
- KeyProtection keyProtection) throws Exception {
SecretKey key = importDefaultKatKey(keyAlgorithm, keyProtection);
Mac mac = Mac.getInstance(macAlgorithm);
try {
mac.init(key);
- fail(message);
+ fail("InvalidKeyException should have been thrown. MAC algorithm: " + macAlgorithm
+ + ", key algorithm: " + keyAlgorithm);
} catch (InvalidKeyException expected) {}
}
+ private SecretKey getDefaultKatKey() {
+ return new SecretKeySpec(KAT_KEY, "HmacSHA1");
+ }
+
private SecretKey importDefaultKatKey() throws Exception {
return importDefaultKatKey("HmacSHA1",
new KeyProtection.Builder(
KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_NONE,
- KeyProperties.DIGEST_SHA1, // TODO: Remove these digests
+ .setDigests(
+ KeyProperties.DIGEST_SHA1,
KeyProperties.DIGEST_SHA224,
KeyProperties.DIGEST_SHA256,
KeyProperties.DIGEST_SHA384,
@@ -364,4 +476,9 @@
new SecretKeySpec(KAT_KEY, keyAlgorithm),
keyProtection);
}
+
+ private static KeyProtection.Builder getWorkingImportParams(
+ @SuppressWarnings("unused") String algorithm) {
+ return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN);
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
index c53e2c3..ff6985a 100644
--- a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
@@ -41,6 +41,10 @@
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
+/**
+ * Tests for algorithm-agnostic functionality of {@code Signature} implementations backed by Android
+ * Keystore.
+ */
public class SignatureTest extends AndroidTestCase {
static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
@@ -321,7 +325,7 @@
+ "f04e3de1460e60e9be7a42b1ddff0c"));
}
- private static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
+ private static final long DAY_IN_MILLIS = TestUtils.DAY_IN_MILLIS;
public void testAlgorithmList() {
// Assert that Android Keystore Provider exposes exactly the expected signature algorithms.
@@ -356,7 +360,43 @@
expectedSigAlgsLowerCase.toArray(new String[0]));
}
- public void testGeneratedSignatureVerifies() throws Exception {
+ public void testAndroidKeyStoreKeysHandledByAndroidKeyStoreProviderWhenSigning()
+ throws Exception {
+ Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyPair keyPair = getKeyPairForSignatureAlgorithm(sigAlgorithm, keyPairs);
+ Signature signature = Signature.getInstance(sigAlgorithm);
+ signature.initSign(keyPair.getPrivate());
+ assertSame(provider, signature.getProvider());
+ } catch (Throwable e) {
+ throw new RuntimeException(sigAlgorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testAndroidKeyStorePublicKeysAcceptedByHighestPriorityProviderWhenVerifying()
+ throws Exception {
+ Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyPair keyPair = getKeyPairForSignatureAlgorithm(sigAlgorithm, keyPairs);
+ Signature signature = Signature.getInstance(sigAlgorithm);
+ signature.initVerify(keyPair.getPublic());
+ } catch (Throwable e) {
+ throw new RuntimeException(sigAlgorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testSignatureGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore()
+ throws Exception {
Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -366,9 +406,8 @@
KeyPair keyPair = getKeyPairForSignatureAlgorithm(sigAlgorithm, keyPairs);
// Generate a signature
- Signature signature = Signature.getInstance(sigAlgorithm);
+ Signature signature = Signature.getInstance(sigAlgorithm, provider);
signature.initSign(keyPair.getPrivate());
- assertSame(provider, signature.getProvider());
byte[] message = "This is a test".getBytes("UTF-8");
signature.update(message);
byte[] sigBytes = signature.sign();
@@ -376,17 +415,92 @@
// Assert that it verifies using our own Provider
assertSignatureVerifiesOneShot(
sigAlgorithm, provider, keyPair.getPublic(), message, sigBytes);
-
- // Assert that it verifies using whatever Provider is chosen by JCA by
- // default for this signature algorithm and public key.
- assertSignatureVerifiesOneShot(
- sigAlgorithm, keyPair.getPublic(), message, sigBytes);
- } catch (Exception e) {
+ } catch (Throwable e) {
throw new RuntimeException(sigAlgorithm + " failed", e);
}
}
}
+ public void testSignatureGeneratedByAndroidKeyStoreVerifiesByHighestPriorityProvider()
+ throws Exception {
+ Collection<KeyPair> keyPairs = getDefaultKatKeyPairs();
+ Collection<KeyPair> keystoreKeyPairs = importDefaultKatKeyPairs();
+
+ Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(keystoreProvider);
+ for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ PrivateKey keystorePrivateKey =
+ getKeyPairForSignatureAlgorithm(sigAlgorithm, keystoreKeyPairs)
+ .getPrivate();
+
+ // Generate a signature
+ Signature signature = Signature.getInstance(sigAlgorithm, keystoreProvider);
+ signature.initSign(keystorePrivateKey);
+ byte[] message = "This is a test".getBytes("UTF-8");
+ signature.update(message);
+ byte[] sigBytes = signature.sign();
+
+ // Assert that it verifies using whatever Provider is chosen by JCA by default for
+ // this signature algorithm and public key.
+ PublicKey publicKey =
+ getKeyPairForSignatureAlgorithm(sigAlgorithm, keyPairs).getPublic();
+ Provider verificationProvider;
+ try {
+ signature = Signature.getInstance(sigAlgorithm);
+ signature.initVerify(publicKey);
+ verificationProvider = signature.getProvider();
+ } catch (InvalidKeyException e) {
+ // No providers support verifying signatures using this algorithm and key.
+ continue;
+ }
+ assertSignatureVerifiesOneShot(
+ sigAlgorithm, verificationProvider, publicKey, message, sigBytes);
+ } catch (Throwable e) {
+ throw new RuntimeException(sigAlgorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testSignatureGeneratedByHighestPriorityProviderVerifiesByAndroidKeyStore()
+ throws Exception {
+ Collection<KeyPair> keyPairs = getDefaultKatKeyPairs();
+ Collection<KeyPair> keystoreKeyPairs = importDefaultKatKeyPairs();
+
+ Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(keystoreProvider);
+ for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ Provider signingProvider = null;
+ try {
+ PrivateKey privateKey =
+ getKeyPairForSignatureAlgorithm(sigAlgorithm, keyPairs).getPrivate();
+
+ // Generate a signature
+ Signature signature;
+ try {
+ signature = Signature.getInstance(sigAlgorithm);
+ signature.initSign(privateKey);
+ signingProvider = signature.getProvider();
+ } catch (InvalidKeyException e) {
+ // No providers support signing using this algorithm and key.
+ continue;
+ }
+ byte[] message = "This is a test".getBytes("UTF-8");
+ signature.update(message);
+ byte[] sigBytes = signature.sign();
+
+ // Assert that the signature verifies using the Android Keystore provider.
+ PublicKey keystorePublicKey =
+ getKeyPairForSignatureAlgorithm(sigAlgorithm, keystoreKeyPairs).getPublic();
+ assertSignatureVerifiesOneShot(
+ sigAlgorithm, keystoreProvider, keystorePublicKey, message, sigBytes);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ sigAlgorithm + " failed, signing provider: " + signingProvider, e);
+ }
+ }
+ }
+
public void testSmallMsgKat() throws Exception {
Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
byte[] message = SHORT_MSG_KAT_MESSAGE;
@@ -417,9 +531,8 @@
algorithm, provider, keyPair.getPublic(), message, goodSigWithBitFlip);
// Sign the message in one go
- Signature signature = Signature.getInstance(algorithm);
+ Signature signature = Signature.getInstance(algorithm, provider);
signature.initSign(keyPair.getPrivate());
- assertSame(provider, signature.getProvider());
signature.update(message);
byte[] generatedSigBytes = signature.sign();
boolean deterministicSignatureScheme =
@@ -451,7 +564,7 @@
}
// Sign the message by feeding it into the Signature one byte at a time
- signature = Signature.getInstance(signature.getAlgorithm());
+ signature = Signature.getInstance(signature.getAlgorithm(), provider);
signature.initSign(keyPair.getPrivate());
for (int i = 0; i < message.length; i++) {
signature.update(message[i]);
@@ -499,9 +612,8 @@
try {
if (algorithm.toLowerCase(Locale.US).startsWith("nonewithrsa")) {
// This algorithm cannot accept large messages
- Signature signature = Signature.getInstance(algorithm);
+ Signature signature = Signature.getInstance(algorithm, provider);
signature.initSign(keyPair.getPrivate());
- assertSame(provider, signature.getProvider());
try {
signature.update(message);
signature.sign();
@@ -514,7 +626,7 @@
byte[] sigBytes = SHORT_MSG_KAT_SIGNATURES.get(
"SHA256" + algorithm.substring("NONE".length()));
assertNotNull(sigBytes);
- signature = Signature.getInstance(algorithm);
+ signature = Signature.getInstance(algorithm, provider);
signature.initVerify(keyPair.getPublic());
try {
signature.update(message);
@@ -534,9 +646,8 @@
algorithm, provider, keyPair.getPublic(), message, goodSigBytes, 718871);
// Sign the message in one go
- Signature signature = Signature.getInstance(algorithm);
+ Signature signature = Signature.getInstance(algorithm, provider);
signature.initSign(keyPair.getPrivate());
- assertSame(provider, signature.getProvider());
signature.update(message);
byte[] generatedSigBytes = signature.sign();
boolean deterministicSignatureScheme =
@@ -566,10 +677,8 @@
}
// Sign the message by feeding it into the Signature one byte at a time
- signature = Signature.getInstance(signature.getAlgorithm());
generatedSigBytes = generateSignatureFedUsingFixedSizeChunks(
algorithm, provider, keyPair.getPrivate(), message, 444307);
- signature.initSign(keyPair.getPrivate());
if (deterministicSignatureScheme) {
MoreAsserts.assertEquals(goodSigBytes, generatedSigBytes);
} else {
@@ -600,114 +709,226 @@
public void testInitVerifySucceedsDespiteMissingAuthorizations() throws Exception {
KeyProtection spec = new KeyProtection.Builder(0).build();
- assertInitVerifySucceeds("SHA256withECDSA", spec);
- assertInitVerifySucceeds("SHA256withRSA", spec);
- assertInitVerifySucceeds("SHA256withRSA/PSS", spec);
+
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ assertInitVerifySucceeds(algorithm, spec);
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
public void testInitSignFailsWhenNotAuthorizedToSign() throws Exception {
- KeyProtection.Builder good = new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_NONE);
int badPurposes = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
| KeyProperties.PURPOSE_VERIFY;
- assertInitSignSucceeds("SHA256withECDSA", good.build());
- assertInitSignThrowsInvalidKeyException("SHA256withECDSA",
- TestUtils.buildUpon(good, badPurposes).build());
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good.build());
+ assertInitSignThrowsInvalidKeyException(algorithm,
+ TestUtils.buildUpon(good, badPurposes).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
- assertInitSignSucceeds("SHA256withRSA", good.build());
- assertInitSignThrowsInvalidKeyException("SHA256withRSA",
- TestUtils.buildUpon(good, badPurposes).build());
+ public void testInitVerifyIgnoresThatNotAuthorizedToVerify() throws Exception {
+ int badPurposes = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
+ | KeyProperties.PURPOSE_SIGN;
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
- assertInitSignSucceeds("SHA256withRSA/PSS", good.build());
- assertInitSignThrowsInvalidKeyException("SHA256withRSA/PSS",
- TestUtils.buildUpon(good, badPurposes).build());
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good.build());
+ assertInitVerifySucceeds(algorithm,
+ TestUtils.buildUpon(good, badPurposes).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
public void testInitSignFailsWhenDigestNotAuthorized() throws Exception {
- KeyProtection.Builder good = new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_SHA384);
- String badDigest = KeyProperties.DIGEST_SHA256;
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good.build());
- assertInitSignSucceeds("SHA384withECDSA", good.build());
- assertInitSignThrowsInvalidKeyException("SHA384withECDSA",
- TestUtils.buildUpon(good).setDigests(badDigest).build());
+ String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ String badDigest =
+ (algorithmUpperCase.startsWith("SHA256")) ? "SHA-384" : "SHA-256";
+ assertInitSignThrowsInvalidKeyException(algorithm,
+ TestUtils.buildUpon(good).setDigests(badDigest).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
- assertInitSignSucceeds("SHA384withRSA", good.build());
- assertInitSignThrowsInvalidKeyException("SHA384withRSA",
- TestUtils.buildUpon(good).setDigests(badDigest).build());
+ public void testInitVerifyIgnoresThatDigestNotAuthorized() throws Exception {
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good.build());
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
- assertInitSignSucceeds("SHA384withRSA/PSS", good.build());
- assertInitSignThrowsInvalidKeyException("SHA384withRSA/PSS",
- TestUtils.buildUpon(good).setDigests(badDigest).build());
+ String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ String badDigest =
+ (algorithmUpperCase.startsWith("SHA256")) ? "SHA-384" : "SHA-256";
+ assertInitVerifySucceeds(algorithm,
+ TestUtils.buildUpon(good).setDigests(badDigest).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
public void testInitSignFailsWhenPaddingNotAuthorized() throws Exception {
- KeyProtection.Builder good = new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_SHA512);
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ String badPaddingScheme;
+ if (algorithmUpperCase.endsWith("WITHECDSA")) {
+ // Test does not apply to ECDSA because ECDSA doesn't any signature padding
+ // schemes.
+ continue;
+ } else if (algorithmUpperCase.endsWith("WITHRSA")) {
+ badPaddingScheme = KeyProperties.SIGNATURE_PADDING_RSA_PSS;
+ } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
+ badPaddingScheme = KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
+ } else {
+ throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
+ }
- // Does not apply to ECDSA because it doesn't any signature padding schemes
- // assertInitSignThrowsInvalidKeyException("SHA256withECDSA", builder.build());
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
- assertInitSignSucceeds("SHA512withRSA", good.build());
- assertInitSignThrowsInvalidKeyException("SHA512withRSA",
- TestUtils.buildUpon(good)
- .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS)
- .build());
+ KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good.build());
+ assertInitSignThrowsInvalidKeyException(algorithm,
+ TestUtils.buildUpon(good).setSignaturePaddings(badPaddingScheme).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
- assertInitSignSucceeds("SHA512withRSA/PSS", good.build());
- assertInitSignThrowsInvalidKeyException("SHA512withRSA/PSS",
- TestUtils.buildUpon(good)
- .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
- .build());
+ public void testInitVerifyIgnoresThatPaddingNotAuthorized() throws Exception {
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ String badPaddingScheme;
+ if (algorithmUpperCase.endsWith("WITHECDSA")) {
+ // Test does not apply to ECDSA because ECDSA doesn't any signature padding
+ // schemes.
+ continue;
+ } else if (algorithmUpperCase.endsWith("WITHRSA")) {
+ badPaddingScheme = KeyProperties.SIGNATURE_PADDING_RSA_PSS;
+ } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
+ badPaddingScheme = KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
+ } else {
+ throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
+ }
+
+ KeyProtection.Builder good = getWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good.build());
+ assertInitVerifySucceeds(algorithm,
+ TestUtils.buildUpon(good).setSignaturePaddings(badPaddingScheme).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
public void testInitSignFailsWhenKeyNotYetValid() throws Exception {
- KeyProtection.Builder good = new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_SHA224);
Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
- assertInitSignSucceeds("SHA224withECDSA", good.build());
- assertInitSignThrowsInvalidKeyException("SHA224withECDSA",
- TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
-
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
- assertInitSignSucceeds("SHA224withRSA", good.build());
- assertInitSignThrowsInvalidKeyException("SHA224withRSA",
- TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
-
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
- assertInitSignSucceeds("SHA224withRSA/PSS", good.build());
- assertInitSignThrowsInvalidKeyException("SHA224withRSA/PSS",
- TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good.build());
+ assertInitSignThrowsInvalidKeyException(algorithm,
+ TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
- public void testInitSignFailsWhenKeyNoLongerValid() throws Exception {
- KeyProtection.Builder good = new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_SHA224);
+ public void testInitVerifyIgnoresThatKeyNotYetValid() throws Exception {
+ Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good.build());
+ assertInitVerifySucceeds(algorithm,
+ TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitSignFailsWhenKeyNoLongerValidForOrigination() throws Exception {
Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
- assertInitSignSucceeds("SHA224withECDSA", good.build());
- assertInitSignThrowsInvalidKeyException("SHA224withECDSA",
- TestUtils.buildUpon(good).setKeyValidityForOriginationEnd(badEndDate).build());
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good.build());
+ assertInitSignThrowsInvalidKeyException(algorithm,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForOriginationEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
- assertInitSignSucceeds("SHA224withRSA", good.build());
- assertInitSignThrowsInvalidKeyException("SHA224withRSA",
- TestUtils.buildUpon(good).setKeyValidityForOriginationEnd(badEndDate).build());
+ public void testInitVerifyIgnoresThatKeyNoLongerValidForOrigination() throws Exception {
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good.build());
+ assertInitVerifySucceeds(algorithm,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForOriginationEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
- good.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
- assertInitSignSucceeds("SHA224withRSA/PSS", good.build());
- assertInitSignThrowsInvalidKeyException("SHA224withRSA/PSS",
- TestUtils.buildUpon(good).setKeyValidityForOriginationEnd(badEndDate).build());
+ public void testInitSignIgnoresThatKeyNoLongerValidForConsumption() throws Exception {
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good.build());
+ assertInitSignSucceeds(algorithm,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForConsumptionEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testInitVerifyIgnoresThatKeyNoLongerValidForConsumption() throws Exception {
+ Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
+ for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ try {
+ KeyProtection.Builder good = getWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good.build());
+ assertInitVerifySucceeds(algorithm,
+ TestUtils.buildUpon(good)
+ .setKeyValidityForConsumptionEnd(badEndDate)
+ .build());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
}
private void assertInitVerifySucceeds(
@@ -732,7 +953,7 @@
TestUtils.getRawResX509Certificate(getContext(), certResId),
keyProtection)
.getPublic();
- Signature signature = Signature.getInstance(signatureAlgorithm);
+ Signature signature = Signature.getInstance(signatureAlgorithm, EXPECTED_PROVIDER_NAME);
signature.initVerify(publicKey);
}
@@ -758,7 +979,7 @@
TestUtils.getRawResX509Certificate(getContext(), certResId),
keyProtection)
.getPrivate();
- Signature signature = Signature.getInstance(signatureAlgorithm);
+ Signature signature = Signature.getInstance(signatureAlgorithm, EXPECTED_PROVIDER_NAME);
signature.initSign(privateKey);
}
@@ -793,7 +1014,7 @@
TestUtils.getRawResX509Certificate(getContext(), certResId),
keyProtection)
.getPrivate();
- Signature signature = Signature.getInstance(signatureAlgorithm);
+ Signature signature = Signature.getInstance(signatureAlgorithm, EXPECTED_PROVIDER_NAME);
try {
signature.initSign(privateKey);
fail(message);
@@ -830,6 +1051,19 @@
getKeyAlgorithmForSignatureAlgorithm(signatureAlgorithm), keyPairs);
}
+ private Collection<KeyPair> getDefaultKatKeyPairs() throws Exception {
+ return Arrays.asList(
+ new KeyPair(
+ TestUtils.getRawResX509Certificate(getContext(), R.raw.rsa_key1_cert)
+ .getPublicKey(),
+ TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key1_pkcs8)),
+ new KeyPair(
+ TestUtils.getRawResX509Certificate(getContext(), R.raw.ec_key1_cert)
+ .getPublicKey(),
+ TestUtils.getRawResPrivateKey(getContext(), R.raw.ec_key1_pkcs8))
+ );
+ }
+
private Collection<KeyPair> importDefaultKatKeyPairs() throws Exception {
return Arrays.asList(
TestUtils.importIntoAndroidKeyStore(
@@ -963,4 +1197,27 @@
+ HexEncoding.encode(signature));
}
}
+
+ private static KeyProtection.Builder getWorkingImportParamsForSigning(String algorithm) {
+ KeyProtection.Builder result = new KeyProtection.Builder(
+ KeyProperties.PURPOSE_SIGN)
+ .setDigests(KeyProperties.DIGEST_NONE);
+ String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ if (algorithmUpperCase.endsWith("WITHECDSA")) {
+ // No need for padding
+ } else if (algorithmUpperCase.endsWith("WITHRSA")) {
+ result.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
+ } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
+ result.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
+ } else {
+ throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
+ }
+ return result;
+ }
+
+ private static KeyProtection.Builder getWorkingImportParamsForVerifying(String algorithm) {
+ return TestUtils.buildUpon(
+ getWorkingImportParamsForSigning(algorithm),
+ KeyProperties.PURPOSE_VERIFY);
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
index 3448ffc..01ddf34 100644
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
@@ -56,6 +56,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import javax.crypto.SecretKey;
@@ -65,6 +66,9 @@
abstract class TestUtils extends Assert {
static final String EXPECTED_CRYPTO_OP_PROVIDER_NAME = "AndroidKeyStoreBCWorkaround";
+ static final String EXPECTED_PROVIDER_NAME = "AndroidKeyStore";
+
+ static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
private TestUtils() {}
@@ -526,6 +530,15 @@
throw new IllegalArgumentException("No KeyPair for key algorithm " + keyAlgorithm);
}
+ static Key getKeyForKeyAlgorithm(String keyAlgorithm, Iterable<? extends Key> keys) {
+ for (Key key : keys) {
+ if (keyAlgorithm.equalsIgnoreCase(key.getAlgorithm())) {
+ return key;
+ }
+ }
+ throw new IllegalArgumentException("No Key for key algorithm " + keyAlgorithm);
+ }
+
static byte[] generateLargeKatMsg(byte[] seed, int msgSizeBytes) throws Exception {
byte[] result = new byte[msgSizeBytes];
MessageDigest digest = MessageDigest.getInstance("SHA-512");
@@ -540,4 +553,38 @@
}
return result;
}
+
+ static byte[] leftPadWithZeroBytes(byte[] array, int length) {
+ if (array.length >= length) {
+ return array;
+ }
+ byte[] result = new byte[length];
+ System.arraycopy(array, 0, result, result.length - array.length, array.length);
+ return result;
+ }
+
+ static boolean contains(int[] array, int value) {
+ for (int element : array) {
+ if (element == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static boolean isHmacAlgorithm(String algorithm) {
+ return algorithm.toUpperCase(Locale.US).startsWith("HMAC");
+ }
+
+ static String getHmacAlgorithmDigest(String algorithm) {
+ String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ if (!algorithmUpperCase.startsWith("HMAC")) {
+ return null;
+ }
+ String result = algorithmUpperCase.substring("HMAC".length());
+ if (result.startsWith("SHA")) {
+ result = "SHA-" + result.substring("SHA".length());
+ }
+ return result;
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 048f956..968a382 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -690,174 +690,192 @@
listener = new MockOnRecordPositionUpdateListener(record);
}
- if (markerPeriodsPerSecond != 0) {
- mMarkerPeriodInFrames = TEST_SR / markerPeriodsPerSecond;
- mMarkerPosition = mMarkerPeriodInFrames;
- assertEquals(AudioRecord.SUCCESS,
- record.setNotificationMarkerPosition(mMarkerPosition));
- } else {
- mMarkerPeriodInFrames = 0;
- }
final int updatePeriodInFrames = (periodsPerSecond == 0)
? 0 : TEST_SR / periodsPerSecond;
- assertEquals(AudioRecord.SUCCESS,
- record.setPositionNotificationPeriod(updatePeriodInFrames));
-
- listener.start(TEST_SR);
- record.startRecording();
- assertEquals(AudioRecord.RECORDSTATE_RECORDING, record.getRecordingState());
- long startTime = System.currentTimeMillis();
-
- // For our tests, we could set test duration by timed sleep or by # frames received.
- // Since we don't know *exactly* when AudioRecord actually begins recording,
- // we end the test by # frames read.
- final int numChannels = AudioFormat.channelCountFromInChannelMask(TEST_CONF);
- final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
- final int bytesPerFrame = numChannels * bytesPerSample;
- // careful about integer overflow in the formula below:
- final int targetSamples = (int)((long)TEST_TIME_MS * TEST_SR * numChannels / 1000);
- final int BUFFER_FRAMES = 512;
- final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
- // TODO: verify behavior when buffer size is not a multiple of frame size.
-
// After starting, there is no guarantee when the first frame of data is read.
long firstSampleTime = 0;
- int samplesRead = 0;
- if (useByteBuffer) {
- ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER_SAMPLES * bytesPerSample);
- while (samplesRead < targetSamples) {
- // the first time through, we read a single frame.
- // this sets the recording anchor position.
- int amount = samplesRead == 0 ? numChannels :
- Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
- amount *= bytesPerSample; // in bytes
- // read always places data at the start of the byte buffer with
- // position and limit are ignored. test this by setting
- // position and limit to arbitrary values here.
- final int lastPosition = 7;
- final int lastLimit = 13;
- byteBuffer.position(lastPosition);
- byteBuffer.limit(lastLimit);
- int ret = blocking ? record.read(byteBuffer, amount) :
- record.read(byteBuffer, amount, AudioRecord.READ_NON_BLOCKING);
- // so long as amount requested in bytes is a multiple of the frame size
- // we expect the byte buffer request to be filled. Caution: the
- // byte buffer data will be in native endian order, not Java order.
- if (blocking) {
- assertEquals(amount, ret);
- } else {
- assertTrue("0 <= " + ret + " <= " + amount, 0 <= ret && ret <= amount);
- }
- // position, limit are not changed by read().
- assertEquals(lastPosition, byteBuffer.position());
- assertEquals(lastLimit, byteBuffer.limit());
- if (samplesRead == 0 && ret > 0) {
- firstSampleTime = System.currentTimeMillis();
- }
- samplesRead += ret / bytesPerSample;
+
+ // blank final variables: all successful paths will initialize the times.
+ final long endTime;
+ final long startTime;
+ final long stopTime;
+
+ try {
+ if (markerPeriodsPerSecond != 0) {
+ mMarkerPeriodInFrames = TEST_SR / markerPeriodsPerSecond;
+ mMarkerPosition = mMarkerPeriodInFrames;
+ assertEquals(AudioRecord.SUCCESS,
+ record.setNotificationMarkerPosition(mMarkerPosition));
+ } else {
+ mMarkerPeriodInFrames = 0;
}
- } else {
- switch (TEST_FORMAT) {
- case AudioFormat.ENCODING_PCM_8BIT: {
- // For 8 bit data, use bytes
- byte[] byteData = new byte[BUFFER_SAMPLES];
+
+ assertEquals(AudioRecord.SUCCESS,
+ record.setPositionNotificationPeriod(updatePeriodInFrames));
+
+ listener.start(TEST_SR);
+ record.startRecording();
+ assertEquals(AudioRecord.RECORDSTATE_RECORDING, record.getRecordingState());
+ startTime = System.currentTimeMillis();
+
+ // For our tests, we could set test duration by timed sleep or by # frames received.
+ // Since we don't know *exactly* when AudioRecord actually begins recording,
+ // we end the test by # frames read.
+ final int numChannels = AudioFormat.channelCountFromInChannelMask(TEST_CONF);
+ final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
+ final int bytesPerFrame = numChannels * bytesPerSample;
+ // careful about integer overflow in the formula below:
+ final int targetSamples = (int)((long)TEST_TIME_MS * TEST_SR * numChannels / 1000);
+ final int BUFFER_FRAMES = 512;
+ final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
+ // TODO: verify behavior when buffer size is not a multiple of frame size.
+
+ int samplesRead = 0;
+ if (useByteBuffer) {
+ ByteBuffer byteBuffer =
+ ByteBuffer.allocateDirect(BUFFER_SAMPLES * bytesPerSample);
while (samplesRead < targetSamples) {
// the first time through, we read a single frame.
// this sets the recording anchor position.
int amount = samplesRead == 0 ? numChannels :
Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
- int ret = blocking ? record.read(byteData, 0, amount) :
- record.read(byteData, 0, amount, AudioRecord.READ_NON_BLOCKING);
+ amount *= bytesPerSample; // in bytes
+ // read always places data at the start of the byte buffer with
+ // position and limit are ignored. test this by setting
+ // position and limit to arbitrary values here.
+ final int lastPosition = 7;
+ final int lastLimit = 13;
+ byteBuffer.position(lastPosition);
+ byteBuffer.limit(lastLimit);
+ int ret = blocking ? record.read(byteBuffer, amount) :
+ record.read(byteBuffer, amount, AudioRecord.READ_NON_BLOCKING);
+ // so long as amount requested in bytes is a multiple of the frame size
+ // we expect the byte buffer request to be filled. Caution: the
+ // byte buffer data will be in native endian order, not Java order.
if (blocking) {
assertEquals(amount, ret);
} else {
- assertTrue("0 <= " + ret + " <= " + amount, 0 <= ret && ret <= amount);
+ assertTrue("0 <= " + ret + " <= " + amount,
+ 0 <= ret && ret <= amount);
}
+ // position, limit are not changed by read().
+ assertEquals(lastPosition, byteBuffer.position());
+ assertEquals(lastLimit, byteBuffer.limit());
if (samplesRead == 0 && ret > 0) {
firstSampleTime = System.currentTimeMillis();
}
- samplesRead += ret;
+ samplesRead += ret / bytesPerSample;
}
- } break;
- case AudioFormat.ENCODING_PCM_16BIT: {
- // For 16 bit data, use shorts
- short[] shortData = new short[BUFFER_SAMPLES];
- while (samplesRead < targetSamples) {
- // the first time through, we read a single frame.
- // this sets the recording anchor position.
- int amount = samplesRead == 0 ? numChannels :
- Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
- int ret = blocking ? record.read(shortData, 0, amount) :
- record.read(shortData, 0, amount, AudioRecord.READ_NON_BLOCKING);
- if (blocking) {
- assertEquals(amount, ret);
- } else {
- assertTrue("0 <= " + ret + " <= " + amount, 0 <= ret && ret <= amount);
+ } else {
+ switch (TEST_FORMAT) {
+ case AudioFormat.ENCODING_PCM_8BIT: {
+ // For 8 bit data, use bytes
+ byte[] byteData = new byte[BUFFER_SAMPLES];
+ while (samplesRead < targetSamples) {
+ // the first time through, we read a single frame.
+ // this sets the recording anchor position.
+ int amount = samplesRead == 0 ? numChannels :
+ Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+ int ret = blocking ? record.read(byteData, 0, amount) :
+ record.read(byteData, 0, amount, AudioRecord.READ_NON_BLOCKING);
+ if (blocking) {
+ assertEquals(amount, ret);
+ } else {
+ assertTrue("0 <= " + ret + " <= " + amount,
+ 0 <= ret && ret <= amount);
+ }
+ if (samplesRead == 0 && ret > 0) {
+ firstSampleTime = System.currentTimeMillis();
+ }
+ samplesRead += ret;
}
- if (samplesRead == 0 && ret > 0) {
- firstSampleTime = System.currentTimeMillis();
+ } break;
+ case AudioFormat.ENCODING_PCM_16BIT: {
+ // For 16 bit data, use shorts
+ short[] shortData = new short[BUFFER_SAMPLES];
+ while (samplesRead < targetSamples) {
+ // the first time through, we read a single frame.
+ // this sets the recording anchor position.
+ int amount = samplesRead == 0 ? numChannels :
+ Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+ int ret = blocking ? record.read(shortData, 0, amount) :
+ record.read(shortData, 0, amount, AudioRecord.READ_NON_BLOCKING);
+ if (blocking) {
+ assertEquals(amount, ret);
+ } else {
+ assertTrue("0 <= " + ret + " <= " + amount,
+ 0 <= ret && ret <= amount);
+ }
+ if (samplesRead == 0 && ret > 0) {
+ firstSampleTime = System.currentTimeMillis();
+ }
+ samplesRead += ret;
}
- samplesRead += ret;
+ } break;
+ case AudioFormat.ENCODING_PCM_FLOAT: {
+ float[] floatData = new float[BUFFER_SAMPLES];
+ while (samplesRead < targetSamples) {
+ // the first time through, we read a single frame.
+ // this sets the recording anchor position.
+ int amount = samplesRead == 0 ? numChannels :
+ Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+ int ret = record.read(floatData, 0, amount, blocking ?
+ AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
+ if (blocking) {
+ assertEquals(amount, ret);
+ } else {
+ assertTrue("0 <= " + ret + " <= " + amount,
+ 0 <= ret && ret <= amount);
+ }
+ if (samplesRead == 0 && ret > 0) {
+ firstSampleTime = System.currentTimeMillis();
+ }
+ samplesRead += ret;
+ }
+ } break;
}
- } break;
- case AudioFormat.ENCODING_PCM_FLOAT: {
- float[] floatData = new float[BUFFER_SAMPLES];
- while (samplesRead < targetSamples) {
- // the first time through, we read a single frame.
- // this sets the recording anchor position.
- int amount = samplesRead == 0 ? numChannels :
- Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
- int ret = record.read(floatData, 0, amount, blocking ?
- AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
- if (blocking) {
- assertEquals(amount, ret);
- } else {
- assertTrue("0 <= " + ret + " <= " + amount, 0 <= ret && ret <= amount);
- }
- if (samplesRead == 0 && ret > 0) {
- firstSampleTime = System.currentTimeMillis();
- }
- samplesRead += ret;
- }
- } break;
}
+
+ // We've read all the frames, now check the record timing.
+ endTime = System.currentTimeMillis();
+ //Log.d(TAG, "first sample time " + (firstSampleTime - startTime)
+ // + " test time " + (endTime - firstSampleTime));
+ // Verify recording starts within 200 ms of record.startRecording() (typical 100ms)
+ // Verify recording completes within 50 ms of expected test time (typical 20ms)
+ assertEquals(0, firstSampleTime - startTime, 200);
+ assertEquals(TEST_TIME_MS, endTime - firstSampleTime, auditRecording ? 1000 : 50);
+
+ // Even though we've read all the frames we want, the events may not be sent to
+ // the listeners (events are handled through a separate internal callback thread).
+ // One must sleep to make sure the last event(s) come in.
+ Thread.sleep(30);
+
+ record.stop();
+ assertEquals(AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
+
+ stopTime = System.currentTimeMillis();
+
+ // stop listening - we should be done.
+ // Caution M behavior and likely much earlier:
+ // we assume no events can happen after stop(), but this may not
+ // always be true as stop can take 100ms to complete (as it may disable
+ // input recording on the hal); thus the event handler may be block with
+ // valid events, issuing right after stop completes. Except for those events,
+ // no other events should show up after stop.
+ // This behavior may change in the future but we account for it here in testing.
+ listener.stop();
+
+ // clean up
+ if (makeSomething != null) {
+ makeSomething.join();
+ }
+
+ } finally {
+ listener.release();
+ // we must release the record immediately as it is a system-wide
+ // resource needed for other tests.
+ record.release();
}
-
- // We've read all the frames, now check the record timing.
- final long endTime = System.currentTimeMillis();
- //Log.d(TAG, "first sample time " + (firstSampleTime - startTime)
- // + " test time " + (endTime - firstSampleTime));
- // Verify recording starts within 200 ms of record.startRecording() (typical 100ms)
- // Verify recording completes within 50 ms of expected test time (typical 20ms)
- assertEquals(0, firstSampleTime - startTime, 200);
- assertEquals(TEST_TIME_MS, endTime - firstSampleTime, auditRecording ? 1000 : 50);
-
- // Even though we've read all the frames we want, the events may not be sent to
- // the listeners (events are handled through a separate internal callback thread).
- // One must sleep to make sure the last event(s) come in.
- Thread.sleep(30);
-
- record.stop();
- assertEquals(AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
-
- final long stopTime = System.currentTimeMillis();
-
- // stop listening - we should be done.
- // Caution M behavior and likely much earlier:
- // we assume no events can happen after stop(), but this may not
- // always be true as stop can take 100ms to complete (as it may disable
- // input recording on the hal); thus the event handler may be block with
- // valid events, issuing right after stop completes. Except for those events,
- // no other events should show up after stop.
- // This behavior may change in the future but we account for it here in testing.
- listener.stop();
-
- // clean up
- if (makeSomething != null) {
- makeSomething.join();
- }
- listener.release();
- record.release();
if (auditRecording) { // don't check timing if auditing (messes up timing)
return;
}
@@ -1005,6 +1023,7 @@
}
public synchronized void release() {
+ stop();
mAudioRecord.setRecordPositionUpdateListener(null);
mAudioRecord = null;
}
diff --git a/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java b/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
new file mode 100644
index 0000000..4139059
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 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.permission.cts;
+
+import android.content.ContentValues;
+import android.content.pm.PackageManager;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+
+/**
+ * Tests for TV API related permissions.
+ */
+public class TvPermissionTest extends AndroidTestCase {
+ private static final String DUMMY_INPUT_ID = "dummy";
+
+ private boolean mHasTvInputFramework;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mHasTvInputFramework = getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_LIVE_TV);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void verifyInsert(Uri uri, String tableName) throws Exception {
+ try {
+ ContentValues values = new ContentValues();
+ getContext().getContentResolver().insert(uri, values);
+ fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
+ } catch (SecurityException e) {
+ // Expected exception
+ }
+ }
+
+ public void verifyUpdate(Uri uri, String tableName) throws Exception {
+ try {
+ ContentValues values = new ContentValues();
+ getContext().getContentResolver().update(uri, values, null, null);
+ fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
+ } catch (SecurityException e) {
+ // Expected exception
+ }
+ }
+
+ public void verifyDelete(Uri uri, String tableName) throws Exception {
+ try {
+ getContext().getContentResolver().delete(uri, null, null);
+ fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
+ } catch (SecurityException e) {
+ // Expected exception
+ }
+ }
+
+ public void testInsertChannels() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyInsert(TvContract.Channels.CONTENT_URI, "channels");
+ }
+
+ public void testUpdateChannels() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyUpdate(TvContract.Channels.CONTENT_URI, "channels");
+ }
+
+ public void testDeleteChannels() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyDelete(TvContract.Channels.CONTENT_URI, "channels");
+ }
+
+ public void testInsertPrograms() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyInsert(TvContract.Programs.CONTENT_URI, "programs");
+ }
+
+ public void testUpdatePrograms() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyUpdate(TvContract.Programs.CONTENT_URI, "programs");
+ }
+
+ public void testDeletePrograms() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyDelete(TvContract.Programs.CONTENT_URI, "programs");
+ }
+}
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
index 41ec835..79406e0 100644
--- a/tests/tests/tv/AndroidManifest.xml
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -22,6 +22,9 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.INJECT_EVENTS" />
+ <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+ <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/voicesettings/Android.mk b/tests/tests/voicesettings/Android.mk
new file mode 100644
index 0000000..71fead6
--- /dev/null
+++ b/tests/tests/voicesettings/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := CtsVoiceSettingsCommon ctstestrunner ctsdeviceutil
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsVoiceSettingsTestCases
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/voicesettings/AndroidManifest.xml b/tests/tests/voicesettings/AndroidManifest.xml
new file mode 100644
index 0000000..bf938f9
--- /dev/null
+++ b/tests/tests/voicesettings/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.voicesettings.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="TestStartActivity"
+ android:label="The Target Activity for VoiceSettings CTS Test">
+ <intent-filter>
+ <action android:name="android.intent.action.TEST_START_ACTIVITY_ZEN_MODE" />
+ <action android:name="android.intent.action.TEST_START_ACTIVITY_AIRPLANE_MODE" />
+ <action android:name="android.intent.action.TEST_START_ACTIVITY_BATTERYSAVER_MODE" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.voicesettings.cts"
+ android:label="CTS tests of android.voicesettings">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/voicesettings/AndroidTest.xml b/tests/tests/voicesettings/AndroidTest.xml
new file mode 100644
index 0000000..0a3974d
--- /dev/null
+++ b/tests/tests/voicesettings/AndroidTest.xml
@@ -0,0 +1,23 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Test module config for VoiceInteraction">
+ <include name="common-config" />
+ <option name="cts-apk-installer:test-file-name" value="CtsVoiceSettingsService.apk" />
+ <option name="run-command:run-command"
+ value="settings put secure voice_interaction_service android.voicesettings.service/.MainInteractionService" />
+ <option name="run-command:teardown-command"
+ value="settings put secure voice_interaction_service com.google.android.googlequicksearchbox/com.google.android.voiceinteraction.GsaVoiceInteractionService" />
+ <option name="cts-apk-installer:test-file-name" value="CtsVoiceSettingsTestCases.apk" />
+</configuration>
diff --git a/tests/tests/voicesettings/common/Android.mk b/tests/tests/voicesettings/common/Android.mk
new file mode 100644
index 0000000..1478ef2
--- /dev/null
+++ b/tests/tests/voicesettings/common/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := CtsVoiceSettingsCommon
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/voicesettings/common/src/android/voicesettings/common/Utils.java b/tests/tests/voicesettings/common/src/android/voicesettings/common/Utils.java
new file mode 100644
index 0000000..44514b0
--- /dev/null
+++ b/tests/tests/voicesettings/common/src/android/voicesettings/common/Utils.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package common.src.android.voicesettings.common;
+
+import android.os.Bundle;
+
+public class Utils {
+ public enum TestcaseType {
+ ZEN_MODE_ON,
+ ZEN_MODE_OFF,
+ AIRPLANE_MODE_ON,
+ AIRPLANE_MODE_OFF,
+ BATTERYSAVER_MODE_ON,
+ BATTERYSAVER_MODE_OFF,
+ }
+ public static final String TESTCASE_TYPE = "Testcase_type";
+ public static final String BROADCAST_INTENT =
+ "android.intent.action.FROM_VOICESETTINGS_CTS_TEST_";
+ public static final int NUM_MINUTES_FOR_ZENMODE = 10;
+
+ public static final String toBundleString(Bundle bundle) {
+ if (bundle == null) {
+ return "*** Bundle is null ****";
+ }
+ StringBuilder buf = new StringBuilder();
+ if (bundle != null) {
+ buf.append("extras: ");
+ for (String s : bundle.keySet()) {
+ buf.append("(" + s + " = " + bundle.get(s) + "), ");
+ }
+ }
+ return buf.toString();
+ }
+}
diff --git a/tests/tests/voicesettings/res/xml/interaction_service.xml b/tests/tests/voicesettings/res/xml/interaction_service.xml
new file mode 100644
index 0000000..bf40892
--- /dev/null
+++ b/tests/tests/voicesettings/res/xml/interaction_service.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:sessionService="android.voicesettings.service.MainInteractionSessionService"
+ android:recognitionService="android.voicesettings.service.MainRecognitionService"
+ android:settingsActivity="android.voicesettings.service.SettingsActivity"
+ android:supportsAssist="false" />
diff --git a/tests/tests/voicesettings/service/Android.mk b/tests/tests/voicesettings/service/Android.mk
new file mode 100644
index 0000000..97866d5
--- /dev/null
+++ b/tests/tests/voicesettings/service/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := CtsVoiceSettingsCommon ctstestrunner ctsdeviceutil
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsVoiceSettingsService
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/tests/voicesettings/service/AndroidManifest.xml b/tests/tests/voicesettings/service/AndroidManifest.xml
new file mode 100644
index 0000000..13671b6
--- /dev/null
+++ b/tests/tests/voicesettings/service/AndroidManifest.xml
@@ -0,0 +1,65 @@
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.voicesettings.service">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <service android:name=".MainInteractionService"
+ android:label="CTS test voice interaction service"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:process=":interactor"
+ android:exported="true">
+ <meta-data android:name="android.voice_interaction"
+ android:resource="@xml/interaction_service" />
+ <intent-filter>
+ <action android:name="android.service.voice.VoiceInteractionService" />
+ </intent-filter>
+ </service>
+ <activity android:name=".VoiceInteractionMain" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIMAIN_ZEN_MODE_ON" />
+ <action android:name="android.intent.action.VIMAIN_ZEN_MODE_OFF" />
+ <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_ON" />
+ <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_OFF" />
+ <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_ON" />
+ <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_OFF" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".SettingsActivity"
+ android:label="Voice Interaction Settings">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <service android:name=".MainInteractionSessionService"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:process=":session">
+ </service>
+ <service android:name=".MainRecognitionService"
+ android:label="CTS Voice Recognition Service">
+ <intent-filter>
+ <action android:name="android.speech.RecognitionService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="android.speech" android:resource="@xml/recognition_service" />
+ </service>
+ </application>
+</manifest>
+
diff --git a/tests/tests/voicesettings/service/res/xml/interaction_service.xml b/tests/tests/voicesettings/service/res/xml/interaction_service.xml
new file mode 100644
index 0000000..bf40892
--- /dev/null
+++ b/tests/tests/voicesettings/service/res/xml/interaction_service.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:sessionService="android.voicesettings.service.MainInteractionSessionService"
+ android:recognitionService="android.voicesettings.service.MainRecognitionService"
+ android:settingsActivity="android.voicesettings.service.SettingsActivity"
+ android:supportsAssist="false" />
diff --git a/tests/tests/voicesettings/service/res/xml/recognition_service.xml b/tests/tests/voicesettings/service/res/xml/recognition_service.xml
new file mode 100644
index 0000000..9d80f24
--- /dev/null
+++ b/tests/tests/voicesettings/service/res/xml/recognition_service.xml
@@ -0,0 +1,17 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<recognition-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="android.voicesettings.service.SettingsActivity" />
diff --git a/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionService.java b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionService.java
new file mode 100644
index 0000000..6302b78
--- /dev/null
+++ b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.service;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionService;
+import android.util.Log;
+import common.src.android.voicesettings.common.Utils;
+
+public class MainInteractionService extends VoiceInteractionService {
+ static final String TAG = "MainInteractionService";
+ private Intent mIntent;
+ private boolean mReady = false;
+
+ @Override
+ public void onReady() {
+ super.onReady();
+ mReady = true;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.i(TAG, "onStartCommand received");
+ mIntent = intent;
+ Log.i(TAG, "received_testcasetype = " + mIntent.getStringExtra(Utils.TESTCASE_TYPE));
+ maybeStart();
+ return START_NOT_STICKY;
+ }
+
+ private void maybeStart() {
+ if (mIntent == null || !mReady) {
+ Log.wtf(TAG, "Can't start session because either intent is null or onReady() "
+ + "is not called yet. mIntent = " + mIntent + ", mReady = " + mReady);
+ } else {
+ Log.i(TAG, "Yay! about to start MainInteractionSession");
+ if (isActiveService(this, new ComponentName(this, getClass()))) {
+ Bundle args = new Bundle();
+ args.putString(Utils.TESTCASE_TYPE, mIntent.getStringExtra(Utils.TESTCASE_TYPE));
+ Log.i(TAG, "xferring_testcasetype = " + args.getString(Utils.TESTCASE_TYPE));
+ showSession(args, 0);
+ } else {
+ Log.wtf(TAG, "**** Not starting MainInteractionService because" +
+ " it is not set as the current voice interaction service");
+ }
+ }
+ }
+}
diff --git a/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSession.java b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSession.java
new file mode 100644
index 0000000..c2b7e18
--- /dev/null
+++ b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSession.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.service;
+
+import static android.provider.Settings.ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE;
+import static android.provider.Settings.EXTRA_DO_NOT_DISTURB_MODE_ENABLED;
+import static android.provider.Settings.EXTRA_DO_NOT_DISTURB_MODE_MINUTES;
+import static android.provider.Settings.ACTION_VOICE_CONTROL_AIRPLANE_MODE;
+import static android.provider.Settings.EXTRA_AIRPLANE_MODE_ENABLED;
+import static android.provider.Settings.ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE;
+import static android.provider.Settings.EXTRA_BATTERY_SAVER_MODE_ENABLED;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.util.Log;
+
+import common.src.android.voicesettings.common.Utils;
+import common.src.android.voicesettings.common.Utils.TestcaseType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MainInteractionSession extends VoiceInteractionSession {
+ static final String TAG = "MainInteractionSession";
+
+ List<MyTask> mUsedTasks = new ArrayList<MyTask>();
+ Context mContext;
+ TestcaseType mTestType;
+
+ MainInteractionSession(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(TAG, "Canceling the Asynctasks in onDestroy()");
+ for (MyTask t : mUsedTasks) {
+ t.cancel(true);
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public void onShow(Bundle args, int showFlags) {
+ super.onShow(args, showFlags);
+ String testCaseType = args.getString(Utils.TESTCASE_TYPE);
+ Log.i(TAG, "received_testcasetype = " + testCaseType);
+ try {
+ mTestType = TestcaseType.valueOf(testCaseType);
+ } catch (IllegalArgumentException e) {
+ Log.wtf(TAG, e);
+ return;
+ } catch (NullPointerException e) {
+ Log.wtf(TAG, e);
+ return;
+ }
+ Intent intent;
+ switch(mTestType) {
+ case ZEN_MODE_ON:
+ intent = new Intent(ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE);
+ intent.putExtra(EXTRA_DO_NOT_DISTURB_MODE_ENABLED, true);
+ intent.putExtra(EXTRA_DO_NOT_DISTURB_MODE_MINUTES, Utils.NUM_MINUTES_FOR_ZENMODE);
+ break;
+ case ZEN_MODE_OFF:
+ intent = new Intent(ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE);
+ intent.putExtra(EXTRA_DO_NOT_DISTURB_MODE_ENABLED, false);
+ break;
+ case AIRPLANE_MODE_ON:
+ intent = new Intent(ACTION_VOICE_CONTROL_AIRPLANE_MODE);
+ intent.putExtra(EXTRA_AIRPLANE_MODE_ENABLED, true);
+ break;
+ case AIRPLANE_MODE_OFF:
+ intent = new Intent(ACTION_VOICE_CONTROL_AIRPLANE_MODE);
+ intent.putExtra(EXTRA_AIRPLANE_MODE_ENABLED, false);
+ break;
+ case BATTERYSAVER_MODE_ON:
+ intent = new Intent(ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE);
+ intent.putExtra(EXTRA_BATTERY_SAVER_MODE_ENABLED, true);
+ break;
+ case BATTERYSAVER_MODE_OFF:
+ intent = new Intent(ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE);
+ intent.putExtra(EXTRA_BATTERY_SAVER_MODE_ENABLED, false);
+ break;
+ default:
+ Log.i(TAG, "Not implemented!");
+ return;
+ }
+ Log.i(TAG, "starting_voiceactivity: " + intent.toString());
+ startVoiceActivity(intent);
+ }
+
+ @Override
+ public void onTaskFinished(Intent intent, int taskId) {
+ // extras contain the info on what the activity started above did.
+ // we probably could verify this also.
+ Bundle extras = intent.getExtras();
+ Log.i(TAG, "in onTaskFinished: testcasetype = " + mTestType + ", intent: " +
+ intent.toString() + Utils.toBundleString(extras));
+ Intent broadcastIntent = new Intent(Utils.BROADCAST_INTENT + mTestType.toString());
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putString(Utils.TESTCASE_TYPE, mTestType.toString());
+ broadcastIntent.putExtras(extras);
+ Log.i(TAG, "sending_broadcast: Bundle = " + Utils.toBundleString(extras) +
+ ", intent = " + broadcastIntent.toString());
+ mContext.sendBroadcast(broadcastIntent);
+ }
+
+ synchronized MyTask newTask() {
+ MyTask t = new MyTask();
+ mUsedTasks.add(t);
+ return t;
+ }
+
+ @Override
+ public void onRequestCompleteVoice(CompleteVoiceRequest request) {
+ CharSequence prompt = request.getVoicePrompt().getVoicePromptAt(0);
+ Log.i(TAG, "in Session testcasetype = " + mTestType +
+ ", onRequestCompleteVoice recvd. message = " + prompt);
+ AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setCompleteReq(true);
+ newTask().execute(asyncTaskArg);
+ }
+
+ @Override
+ public void onRequestAbortVoice(AbortVoiceRequest request) {
+ AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setCompleteReq(false);
+ Log.i(TAG, "in Session sending sendAbortResult. ");
+ newTask().execute(asyncTaskArg);
+ }
+
+ private class AsyncTaskArg {
+ CompleteVoiceRequest mCompReq;
+ AbortVoiceRequest mAbortReq;
+ boolean isCompleteRequest = true;
+
+ AsyncTaskArg setRequest(CompleteVoiceRequest r) {
+ mCompReq = r;
+ return this;
+ }
+
+ AsyncTaskArg setRequest(AbortVoiceRequest r) {
+ mAbortReq = r;
+ return this;
+ }
+
+ AsyncTaskArg setCompleteReq(boolean flag) {
+ isCompleteRequest = flag;
+ return this;
+ }
+ }
+
+ private class MyTask extends AsyncTask<AsyncTaskArg, Void, Void> {
+ @Override
+ protected Void doInBackground(AsyncTaskArg... params) {
+ AsyncTaskArg arg = params[0];
+ Log.i(TAG, "in MyTask - doInBackground: testType = " +
+ MainInteractionSession.this.mTestType);
+ if (arg.isCompleteRequest) {
+ arg.mCompReq.sendCompleteResult(new Bundle());
+ } else {
+ arg.mAbortReq.sendAbortResult(new Bundle());
+ }
+ return null;
+ }
+ }
+}
diff --git a/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSessionService.java b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSessionService.java
new file mode 100644
index 0000000..2b302b8
--- /dev/null
+++ b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSessionService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.service;
+
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.service.voice.VoiceInteractionSessionService;
+
+public class MainInteractionSessionService extends VoiceInteractionSessionService {
+ @Override
+ public VoiceInteractionSession onNewSession(Bundle args) {
+ return new MainInteractionSession(this);
+ }
+}
diff --git a/tests/tests/voicesettings/service/src/android/voicesettings/service/MainRecognitionService.java b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainRecognitionService.java
new file mode 100644
index 0000000..9b0e95d
--- /dev/null
+++ b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainRecognitionService.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.service;
+
+import android.content.Intent;
+import android.speech.RecognitionService;
+import android.util.Log;
+
+/**
+ * Stub recognition service needed to be a complete voice interactor.
+ */
+public class MainRecognitionService extends RecognitionService {
+ private static final String TAG = "MainRecognitionService";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i(TAG, "onCreate");
+ }
+
+ @Override
+ protected void onStartListening(Intent recognizerIntent, Callback listener) {
+ Log.i(TAG, "onStartListening");
+ }
+
+ @Override
+ protected void onCancel(Callback listener) {
+ Log.i(TAG, "onCancel");
+ }
+
+ @Override
+ protected void onStopListening(Callback listener) {
+ Log.i(TAG, "onStopListening");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.i(TAG, "onDestroy");
+ }
+}
diff --git a/tests/tests/voicesettings/service/src/android/voicesettings/service/SettingsActivity.java b/tests/tests/voicesettings/service/src/android/voicesettings/service/SettingsActivity.java
new file mode 100644
index 0000000..140bca4
--- /dev/null
+++ b/tests/tests/voicesettings/service/src/android/voicesettings/service/SettingsActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.service;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Stub activity to test out settings selection for voice interactor.
+ */
+public class SettingsActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/tests/tests/voicesettings/service/src/android/voicesettings/service/VoiceInteractionMain.java b/tests/tests/voicesettings/service/src/android/voicesettings/service/VoiceInteractionMain.java
new file mode 100644
index 0000000..adc2980
--- /dev/null
+++ b/tests/tests/voicesettings/service/src/android/voicesettings/service/VoiceInteractionMain.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.service;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.util.Log;
+
+import common.src.android.voicesettings.common.Utils;
+
+public class VoiceInteractionMain extends Activity {
+ static final String TAG = "VoiceInteractionMain";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = new Intent();
+ String testCaseType = getIntent().getStringExtra(Utils.TESTCASE_TYPE);
+ Log.i(TAG, "received_testcasetype = " + testCaseType);
+ intent.putExtra(Utils.TESTCASE_TYPE, testCaseType);
+ intent.setComponent(new ComponentName(this, MainInteractionService.class));
+ ComponentName serviceName = startService(intent);
+ Log.i(TAG, "Started service: " + serviceName);
+ }
+}
diff --git a/tests/tests/voicesettings/src/android/voicesettings/cts/AirplaneModeTest.java b/tests/tests/voicesettings/src/android/voicesettings/cts/AirplaneModeTest.java
new file mode 100644
index 0000000..8abe396
--- /dev/null
+++ b/tests/tests/voicesettings/src/android/voicesettings/cts/AirplaneModeTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.cts;
+
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.Log;
+
+import common.src.android.voicesettings.common.Utils;
+
+public class AirplaneModeTest extends VoiceSettingsTestBase {
+ static final String TAG = "AirplaneModeTest";
+
+ private static final int AIRPLANE_MODE_IS_OFF = 0;
+ private static final int AIRPLANE_MODE_IS_ON = 1;
+
+ public AirplaneModeTest() {
+ super();
+ }
+
+ public void testAll() throws Exception {
+ startTestActivity("AIRPLANE_MODE");
+ int mode = getMode();
+ Log.i(TAG, "Before testing, AIRPLANE_MODE is set to: " + mode);
+ if (mode == AIRPLANE_MODE_IS_OFF) {
+ // mode is currently OFF.
+ // run a test to turn it on.
+ // After successful run of the test, run a test to turn it back off.
+ if (!runTest(Utils.TestcaseType.AIRPLANE_MODE_ON, AIRPLANE_MODE_IS_ON)) {
+ // the test failed. don't test the next one.
+ return;
+ }
+ runTest(Utils.TestcaseType.AIRPLANE_MODE_OFF, AIRPLANE_MODE_IS_OFF);
+ } else {
+ // mode is currently ON.
+ // run a test to turn it off.
+ // After successful run of the test, run a test to turn it back on.
+ if (!runTest(Utils.TestcaseType.AIRPLANE_MODE_OFF, AIRPLANE_MODE_IS_OFF)) {
+ // the test failed. don't test the next one.
+ return;
+ }
+ runTest(Utils.TestcaseType.AIRPLANE_MODE_ON, AIRPLANE_MODE_IS_ON);
+ }
+ }
+
+ private boolean runTest(Utils.TestcaseType test, int expectedMode) throws Exception {
+ if (!startTestAndWaitForBroadcast(test)) {
+ return false;
+ }
+
+ // verify the test results
+ int mode = getMode();
+ Log.i(TAG, "After testing, AIRPLANE_MODE is set to: " + mode);
+ assertEquals(expectedMode, mode);
+ Log.i(TAG, "Successfully Tested: " + test);
+ return true;
+ }
+
+ private int getMode() throws Exception {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON);
+ }
+}
diff --git a/tests/tests/voicesettings/src/android/voicesettings/cts/BatterySaverModeTest.java b/tests/tests/voicesettings/src/android/voicesettings/cts/BatterySaverModeTest.java
new file mode 100644
index 0000000..3d1357a
--- /dev/null
+++ b/tests/tests/voicesettings/src/android/voicesettings/cts/BatterySaverModeTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.cts;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.util.Log;
+
+import common.src.android.voicesettings.common.Utils;
+
+public class BatterySaverModeTest extends VoiceSettingsTestBase {
+ static final String TAG = "BatterySaverModeTest";
+
+ public BatterySaverModeTest() {
+ super();
+ }
+
+ public void testAll() throws Exception {
+ startTestActivity("BATTERYSAVER_MODE");
+ boolean modeIsOn = isModeOn();
+ Log.i(TAG, "Before testing, BATTERYSAVER_MODE is set to: " + modeIsOn);
+ if (modeIsOn) {
+ // mode is currently ON.
+ // run a test to turn it off.
+ // After successful run of the test, run a test to turn it back on.
+ if (!runTest(Utils.TestcaseType.BATTERYSAVER_MODE_OFF, false)) {
+ // the test failed. don't test the next one.
+ return;
+ }
+ runTest(Utils.TestcaseType.BATTERYSAVER_MODE_ON, true);
+ } else {
+ // mode is currently OFF.
+ // run a test to turn it on.
+ // After successful run of the test, run a test to turn it back off.
+ if (!runTest(Utils.TestcaseType.BATTERYSAVER_MODE_ON, true)) {
+ // the test failed. don't test the next one.
+ return;
+ }
+ runTest(Utils.TestcaseType.BATTERYSAVER_MODE_OFF, false);
+ }
+ }
+
+ private boolean runTest(Utils.TestcaseType test, boolean expectedMode) throws Exception {
+ if (!startTestAndWaitForBroadcast(test)) {
+ return false;
+ }
+
+ // Verify the test results
+ // Since CTS test needs the device to be connected to the host computer via USB,
+ // Batter Saver mode can't be turned on/off.
+ // The most we can do is that the broadcast frmo MainInteractionSession is received
+ // because that signals the firing and completion of BatterySaverModeVoiceActivity
+ // caused by the intent to set Battery Saver mode.
+ return true;
+ }
+
+ private boolean isModeOn() {
+ PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ return powerManager.isPowerSaveMode();
+ }
+}
diff --git a/tests/tests/voicesettings/src/android/voicesettings/cts/TestStartActivity.java b/tests/tests/voicesettings/src/android/voicesettings/cts/TestStartActivity.java
new file mode 100644
index 0000000..cef29b1
--- /dev/null
+++ b/tests/tests/voicesettings/src/android/voicesettings/cts/TestStartActivity.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.util.Log;
+
+import common.src.android.voicesettings.common.Utils;
+
+public class TestStartActivity extends Activity {
+ static final String TAG = "TestStartActivity";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, " in onCreate");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.i(TAG, " in onResume");
+ }
+
+ void startTest(String testCaseType) {
+ Intent intent = new Intent();
+ Log.i(TAG, "received_testcasetype = " + testCaseType);
+ intent.putExtra(Utils.TESTCASE_TYPE, testCaseType);
+ intent.setAction("android.intent.action.VIMAIN_" + testCaseType);
+ intent.setComponent(new ComponentName("android.voicesettings.service",
+ "android.voicesettings.service.VoiceInteractionMain"));
+ startActivity(intent);
+ }
+
+ @Override
+ protected void onPause() {
+ Log.i(TAG, " in onPause");
+ super.onPause();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Log.i(TAG, " in onStart");
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ Log.i(TAG, " in onRestart");
+ }
+
+ @Override
+ protected void onStop() {
+ Log.i(TAG, " in onStop");
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.i(TAG, " in onDestroy");
+ super.onDestroy();
+ }
+}
diff --git a/tests/tests/voicesettings/src/android/voicesettings/cts/VoiceSettingsTestBase.java b/tests/tests/voicesettings/src/android/voicesettings/cts/VoiceSettingsTestBase.java
new file mode 100644
index 0000000..5386497
--- /dev/null
+++ b/tests/tests/voicesettings/src/android/voicesettings/cts/VoiceSettingsTestBase.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import common.src.android.voicesettings.common.Utils;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class VoiceSettingsTestBase extends ActivityInstrumentationTestCase2<TestStartActivity> {
+ static final String TAG = "VoiceSettingsTestBase";
+ protected static final int TIMEOUT_MS = 20 * 1000;
+
+ protected Context mContext;
+ protected Bundle mResultExtras;
+ private CountDownLatch mLatch;
+ private ActivityDoneReceiver mActivityDoneReceiver = null;
+ private TestStartActivity mActivity;
+ private Utils.TestcaseType mTestCaseType;
+
+ public VoiceSettingsTestBase() {
+ super(TestStartActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getTargetContext();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mContext.unregisterReceiver(mActivityDoneReceiver);
+ super.tearDown();
+ }
+
+ protected void startTestActivity(String intentSuffix) {
+ Intent intent = new Intent();
+ intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + intentSuffix);
+ intent.setComponent(new ComponentName(getInstrumentation().getContext(),
+ TestStartActivity.class));
+ setActivityIntent(intent);
+ mActivity = getActivity();
+ }
+
+ protected void registerBroadcastReceiver(Utils.TestcaseType testCaseType) throws Exception {
+ mTestCaseType = testCaseType;
+ mLatch = new CountDownLatch(1);
+ if (mActivityDoneReceiver != null) {
+ mContext.unregisterReceiver(mActivityDoneReceiver);
+ }
+ mActivityDoneReceiver = new ActivityDoneReceiver();
+ mContext.registerReceiver(mActivityDoneReceiver,
+ new IntentFilter(Utils.BROADCAST_INTENT + testCaseType.toString()));
+ }
+
+ protected boolean startTestAndWaitForBroadcast(Utils.TestcaseType testCaseType)
+ throws Exception {
+ Log.i(TAG, "Begin Testing: " + testCaseType);
+ registerBroadcastReceiver(testCaseType);
+ mActivity.startTest(testCaseType.toString());
+ if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Failed to receive broadcast in " + TIMEOUT_MS + "msec");
+ return false;
+ }
+ return true;
+ }
+
+ class ActivityDoneReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(
+ Utils.BROADCAST_INTENT +
+ VoiceSettingsTestBase.this.mTestCaseType.toString())) {
+ Bundle extras = intent.getExtras();
+ Log.i(TAG, "received_broadcast for " + Utils.toBundleString(extras));
+ VoiceSettingsTestBase.this.mResultExtras = extras;
+ mLatch.countDown();
+ }
+ }
+ }
+}
diff --git a/tests/tests/voicesettings/src/android/voicesettings/cts/ZenModeTest.java b/tests/tests/voicesettings/src/android/voicesettings/cts/ZenModeTest.java
new file mode 100644
index 0000000..8c2efbe
--- /dev/null
+++ b/tests/tests/voicesettings/src/android/voicesettings/cts/ZenModeTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicesettings.cts;
+
+import static android.provider.Settings.EXTRA_DO_NOT_DISTURB_MODE_ENABLED;
+import static android.provider.Settings.EXTRA_DO_NOT_DISTURB_MODE_MINUTES;
+
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.Log;
+
+import common.src.android.voicesettings.common.Utils;
+
+public class ZenModeTest extends VoiceSettingsTestBase {
+ static final String TAG = "ZenModeTest";
+
+ // The following are hidden in frameworks/base/core/java/android/provider/Settings.java
+ // If they weren't, we could have used them directly here.
+ private static final String ZEN_MODE = "zen_mode";
+ private static final int ZEN_MODE_IS_OFF = 0;
+ private static final int ZEN_MODE_IS_ALARMS = 3;
+
+ public ZenModeTest() {
+ super();
+ }
+
+ public void testAll() throws Exception {
+ startTestActivity("ZEN_MODE");
+ int mode = getMode();
+ Log.i(TAG, "Before testing, zen-mode is set to: " + mode);
+ if (mode == ZEN_MODE_IS_OFF) {
+ // mode is currently OFF.
+ // run a test to turn it on.
+ // After successful run of the test, run a test to turn it back off.
+ if (!runTest(Utils.TestcaseType.ZEN_MODE_ON, ZEN_MODE_IS_ALARMS)) {
+ // the test failed. don't test the next one.
+ return;
+ }
+ runTest(Utils.TestcaseType.ZEN_MODE_OFF, ZEN_MODE_IS_OFF);
+ } else {
+ // mode is currently ON.
+ // run a test to turn it off.
+ // After successful run of the test, run a test to turn it back on.
+ if (!runTest(Utils.TestcaseType.ZEN_MODE_OFF, ZEN_MODE_IS_OFF)) {
+ // the test failed. don't test the next one.
+ return;
+ }
+ runTest(Utils.TestcaseType.ZEN_MODE_ON, ZEN_MODE_IS_ALARMS);
+ }
+ }
+
+ private boolean runTest(Utils.TestcaseType test, int expectedMode) throws Exception {
+ if (!startTestAndWaitForBroadcast(test)) {
+ return false;
+ }
+
+ // verify the test results
+ int mode = getMode();
+ Log.i(TAG, "After testing, zen-mode is set to: " + mode);
+ assertEquals(expectedMode, mode);
+ Log.i(TAG, "results_received: " + Utils.toBundleString(mResultExtras));
+ assertNotNull(mResultExtras);
+ if (expectedMode == ZEN_MODE_IS_ALARMS) {
+ assertTrue(mResultExtras.getBoolean(EXTRA_DO_NOT_DISTURB_MODE_ENABLED));
+ assertEquals(Utils.NUM_MINUTES_FOR_ZENMODE,
+ mResultExtras.getInt(EXTRA_DO_NOT_DISTURB_MODE_MINUTES));
+ } else {
+ assertFalse(mResultExtras.getBoolean(EXTRA_DO_NOT_DISTURB_MODE_ENABLED));
+ }
+ Log.i(TAG, "Successfully Tested: " + test);
+ return true;
+ }
+
+ private int getMode() throws Exception {
+ return Settings.Global.getInt(mContext.getContentResolver(), ZEN_MODE);
+ }
+}