Merge "Add a jank test with Leanback UI." into mnc-dev
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index bde866e..d4d963f 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -213,6 +213,7 @@
CtsHostUi \
CtsMonkeyTestCases \
CtsThemeHostTestCases \
+ CtsUsageHostTestCases \
CtsSecurityHostTestCases \
CtsUsbTests
diff --git a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
index 27da8fc..f4f0d56 100644
--- a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
@@ -17,10 +17,11 @@
import its.device
import its.objects
import its.target
-import pylab
-import os.path
import matplotlib
import matplotlib.pyplot
+import numpy
+import os.path
+import pylab
def main():
"""Test that the android.noiseReduction.mode param is applied when set.
@@ -34,6 +35,8 @@
"""
NAME = os.path.basename(__file__).split(".")[0]
+ RELATIVE_ERROR_TOLERANCE = 0.1
+
# List of variances for Y,U,V.
variances = [[],[],[]]
@@ -96,18 +99,27 @@
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 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])
+ # Smaller variance is better
+ # Verify OFF(0) is not better than FAST(1)
+ assert(variances[j][0] >
+ variances[j][1] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+ # Verify FAST(1) is not better than HQ(2)
+ assert(variances[j][1] >
+ variances[j][2] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+ # Verify HQ(2) is better than OFF(0)
+ assert(variances[j][0] > variances[j][2])
+ if its.caps.noise_reduction_mode(props, 3):
+ # Verify OFF(0) is not better than MINIMAL(3)
+ assert(variances[j][0] >
+ variances[j][3] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+ # Verify MINIMAL(3) is not better than HQ(2)
+ assert(variances[j][3] >
+ variances[j][2] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+ if its.caps.noise_reduction_mode(props, 4):
+ # Verify ZSL(4) is close to OFF(0)
+ assert(numpy.isclose(variances[j][4], variances[j][0],
+ RELATIVE_ERROR_TOLERANCE))
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 c2657c7..aeeae33 100644
--- a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
@@ -20,6 +20,7 @@
import math
import matplotlib
import matplotlib.pyplot
+import numpy
import os.path
import pylab
@@ -37,6 +38,8 @@
NAME = os.path.basename(__file__).split(".")[0]
+ RELATIVE_ERROR_TOLERANCE = 0.1
+
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
@@ -78,7 +81,7 @@
print "Ref variances:", ref_variance
for nr_mode in range(5):
- # Skipp unavailable modes
+ # Skip unavailable modes
if not its.caps.noise_reduction_mode(props, nr_mode):
nr_modes_reported.append(nr_mode)
variances.append(0)
@@ -111,21 +114,28 @@
matplotlib.pyplot.savefig("%s_plot_%s_variances.png" %
(NAME, reprocess_format))
- assert(nr_modes_reported == [0,1,2,3,4]
+ assert(nr_modes_reported == [0,1,2,3,4])
- # 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])
+ for j in range(3):
+ # Smaller variance is better
+ # Verify OFF(0) is not better than FAST(1)
+ assert(variances[0][j] >
+ variances[1][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+ # Verify FAST(1) is not better than HQ(2)
+ assert(variances[1][j] >
+ variances[2][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+ # Verify HQ(2) is better than OFF(0)
+ assert(variances[0][j] > variances[2][j])
+ if its.caps.noise_reduction_mode(props, 3):
+ # Verify OFF(0) is not better than MINIMAL(3)
+ assert(variances[0][j] >
+ variances[3][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+ # Verify MINIMAL(3) is not better than HQ(2)
+ assert(variances[3][j] >
+ variances[2][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+ # Verify ZSL(4) is close to OFF(0)
+ assert(numpy.isclose(variances[4][j], variances[0][j],
+ RELATIVE_ERROR_TOLERANCE))
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 77208e7..910cda2 100644
--- a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
@@ -20,6 +20,7 @@
import math
import matplotlib
import matplotlib.pyplot
+import numpy
import os.path
import pylab
@@ -153,28 +154,48 @@
print "Sharpness with EE mode [0,1,2,3] for %s reprocess:" % \
(reprocess_format) , sharpnesses
- # 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 sharpness of reprocess captures are similar to sharpness of
- # regular captures.
+ # Verify HQ(2) is sharper than OFF(0)
+ assert(sharpness_regular[2] > sharpness_regular[0])
+
+ # Verify ZSL(3) is similar to OFF(0)
+ assert(numpy.isclose(sharpness_regular[3], sharpness_regular[0],
+ THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+ # Verify OFF(0) is not sharper than FAST(1)
+ assert(sharpness_regular[1] >
+ sharpness_regular[0] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+ # Verify FAST(1) is not sharper than HQ(2)
+ assert(sharpness_regular[2] >
+ sharpness_regular[1] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
for reprocess_format in range(len(reprocess_formats)):
- 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])
+ # Verify HQ(2) is sharper than OFF(0)
+ assert(sharpnesses_reprocess[reprocess_format][2] >
+ sharpnesses_reprocess[reprocess_format][0])
+
+ # Verify ZSL(3) is similar to OFF(0)
+ assert(numpy.isclose(sharpnesses_reprocess[reprocess_format][3],
+ sharpnesses_reprocess[reprocess_format][0],
+ THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+ # Verify OFF(0) is not sharper than FAST(1)
+ assert(sharpnesses_reprocess[reprocess_format][1] >
+ sharpnesses_reprocess[reprocess_format][0] *
+ (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+ # Verify FAST(1) is not sharper than HQ(2)
+ assert(sharpnesses_reprocess[reprocess_format][2] >
+ sharpnesses_reprocess[reprocess_format][1] *
+ (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+ # Verify reprocessing HQ(2) is similar to regular HQ(2) relative to
+ # OFF(0)
+ assert(numpy.isclose(sharpness_reprocess[reprocess_format][2] /
+ sharpness_reprocess[reprocess_format][0],
+ sharpness_regular[2] / sharpness_regular[0],
+ THRESHOLD_RELATIVE_SHARPNESS_DIFF))
if __name__ == '__main__':
main()
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 4597108..39302c1 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -56,6 +56,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+ <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<!-- Needed by the Audio Quality Verifier to store the sound samples that will be mailed. -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -352,6 +353,28 @@
<meta-data android:name="test_category" android:value="@string/test_category_security" />
</activity>
+ <activity android:name=".security.FingerprintBoundKeysTest"
+ android:label="@string/sec_fingerprint_bound_key_test"
+ android:configChanges="keyboardHidden|orientation|screenSize" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_security" />
+ <meta-data android:name="test_excluded_features"
+ android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+ </activity>
+ <activity android:name=".security.ScreenLockBoundKeysTest"
+ android:label="@string/sec_lock_bound_key_test"
+ android:configChanges="keyboardHidden|orientation|screenSize" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_security" />
+ <meta-data android:name="test_excluded_features"
+ android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+ </activity>
<activity android:name=".security.LockConfirmBypassTest"
android:label="@string/lock_confirm_test_title"
android:configChanges="keyboardHidden|orientation|screenSize" >
@@ -429,6 +452,10 @@
android:label="@string/nfc_ndef_push_receiver"
android:configChanges="keyboardHidden|orientation|screenSize" />
+ <activity android:name=".nfc.LlcpVersionActivity"
+ android:label="@string/nfc_llcp_version_check"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
<activity android:name=".nfc.TagVerifierActivity"
android:label="@string/nfc_tag_verifier"
android:configChanges="keyboardHidden|orientation|screenSize" />
diff --git a/apps/CtsVerifier/res/layout/sec_screen_lock_keys_main.xml b/apps/CtsVerifier/res/layout/sec_screen_lock_keys_main.xml
new file mode 100644
index 0000000..af53335
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/sec_screen_lock_keys_main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="10dip"
+ >
+
+ <Button android:id="@+id/sec_start_test_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:text="@string/sec_start_test"
+ />
+
+ <include android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ layout="@layout/pass_fail_buttons"
+ />
+
+</RelativeLayout>
+
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 8c743bf..d8637ca 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -124,6 +124,24 @@
<string name="da_lock_success">It appears the screen was locked successfully!</string>
<string name="da_lock_error">It does not look like the screen was locked...</string>
+ <!-- Strings for lock bound keys test -->
+ <string name="sec_lock_bound_key_test">Lock Bound Keys Test</string>
+ <string name="sec_lock_bound_key_test_info">
+ This test ensures that Keystore cryptographic keys that are bound to lock screen authentication
+ are unusable without a recent enough authentication. You need to set up a screen lock in order to
+ complete this test. If available, this test should be run by using fingerprint authentication
+ as well as PIN/pattern/password authentication.
+ </string>
+ <string name="sec_fingerprint_bound_key_test">Fingerprint Bound Keys Test</string>
+ <string name="sec_fingerprint_bound_key_test_info">
+ This test ensures that Keystore cryptographic keys that are bound to fingerprint authentication
+ are unusable without an authentication. You need to set up a fingerprint order to
+ complete this test.
+ </string>
+ <string name="sec_fp_dialog_message">Authenticate now with fingerprint</string>
+ <string name="sec_fp_auth_failed">Authentication failed</string>
+ <string name="sec_start_test">Start Test</string>
+
<!-- Strings for BluetoothActivity -->
<string name="bluetooth_test">Bluetooth Test</string>
<string name="bluetooth_test_info">The Bluetooth Control tests check whether or not the device
@@ -378,6 +396,7 @@
<string name="nfc_pee_2_pee">Peer-to-Peer Data Exchange</string>
<string name="nfc_ndef_push_sender">NDEF Push Sender</string>
<string name="nfc_ndef_push_receiver">NDEF Push Receiver</string>
+ <string name="nfc_llcp_version_check">LLCP version check</string>
<string name="nfc_tag_verification">Tag Verification</string>
<string name="nfc_ndef">NDEF</string>
@@ -399,6 +418,13 @@
<string name="nfc_ndef_push_receive_failure">Failed to receive the correct NDEF push
message.</string>
+ <string name="nfc_llcp_version_check_info">This test requires two candidate devices
+ with NFC enabled to exchange P2P messages. Start the \"LLCP version check\" test on
+ the other candidate device also, and touch the devices back to back. This test
+ then verifies that the candidate device correctly advises the LLCP version as 1.2</string>
+ <string name="nfc_llcp_version_check_failure">The candidate devices does not report LLCP
+ version 1.2 or higher.</string>
+ <string name="nfc_llcp_version_check_success">The candidate device has a valid LLCP version.</string>
<string name="nfc_tag_verifier">NFC Tag Verifier</string>
<string name="nfc_tag_verifier_info">Follow the on-screen instructions to write and read
a tag of the chosen technology.</string>
@@ -1661,93 +1687,90 @@
<string name="js_any_connectivity_test">Device with no connectivity will not execute a job with an unmetered connectivity constraint.</string>
<string name="js_no_connectivity_test">Device with no connectivity will still execute a job with no connectivity constraints.</string>
- <!-- String for Live Channels app Tests -->
+ <!-- String for the bundled TV app Tests -->
- <string name="tv_input_discover_test">3rd-party TV input app discoverability test</string>
+ <string name="tv_input_discover_test">3rd-party TV input test</string>
<string name="tv_input_discover_test_info">
- This test verifies that the pre-loaded Live Channels app is invoked via intents and issues
- appropriate calls to framework APIs, so that TV input apps work properly with the pre-loaded
- Live Channels app.
+ Verify that the bundled TV app launches via Intent and calls the proper API to discover
+ 3rd-party TV inputs.
</string>
<string name="tv_input_discover_test_go_to_setup">
- Press the \"Launch Live Channels\" button, and set up the newly installed TV input:
+ Select the \"Launch TV app\" button and set up the newly installed TV input:
\"CTS Verifier\".
</string>
<string name="tv_input_discover_test_verify_setup">
Setup activity must have been started.
</string>
<string name="tv_input_discover_test_tune_to_channel">
- Press the \"Launch Live Channels\" button, and tune to the channel named \"Dummy\" from
- \"CTS Verifier\" input. If necessary, configure the channel to be visible.
+ Select the \"Launch TV app\" button and tune to the \"Dummy\" channel from \"CTS Verifier\"
+ input. If necessary, configure the channel to be visible.
</string>
<string name="tv_input_discover_test_verify_tune">
Tune command must be called.
</string>
<string name="tv_input_discover_test_verify_overlay_view">
- Overlay view must be shown. Verify that there is a text view displaying \"Overlay View Dummy Text\"
- when you tune to the \"Dummy\" channel.
+ Verify that the overlay appears and displays the text \"Overlay View Dummy Text\" when you tune
+ to the \"Dummy\" channel.
</string>
<string name="tv_input_discover_test_verify_global_search">
- Live Channels app should provide query results for 3rd-party input\'s channels and programs on
- global search requests.
+ Verify the TV app provides query results for 3rd-party input\'s channels and programs in
+ global search results.
</string>
<string name="tv_input_discover_test_go_to_epg">
- Press the \"Launch EPG\" button, and locate the channel named \"Dummy\".
+ Select the \"Launch EPG\" button and locate the \"Dummy\" channel.
</string>
<string name="tv_input_discover_test_verify_epg">
- Do you see the programs named \"Dummy Program\" and its description
+ Do you see the programs named \"Dummy Program\" and their descriptions
"Dummy Program Description" in the EPG?
</string>
<string name="tv_input_discover_test_yes">Yes</string>
- <string name="tv_parental_control_test">Live Channels app parental control test</string>
+ <string name="tv_parental_control_test">TV app parental controls test</string>
<string name="tv_parental_control_test_info">
- This test verifies that the default Live Channels app invokes proper parental control APIs in
- the framework.
+ Verify that the bundled TV app calls the parental controls API.
</string>
<string name="tv_parental_control_test_turn_on_parental_control">
- Press the \"Launch Live Channels\" button, and turn on the parental control. If it\'s on
- already, turn it off and on again.
+ Select the \"Launch TV app\" button and turn on the parental controls. If parental controls are
+ on already, turn it off and on again.
</string>
<string name="tv_parental_control_test_verify_receive_broadcast1">
TV input service must have received ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED broadcast.
</string>
<string name="tv_parental_control_test_block_tv_ma">
- Press the \"Launch Live Channels\" button, and block \"Fake\" rating for \"CtsVerifier\" rating
- system in the parental control settings. You may have to enable the rating system if it is
- disabled by default. If it\'s already blocked, unblock, save, and then block again.
+ Select the \"Launch TV app\" button and block the \"Fake\" rating for \"CtsVerifier\" rating
+ system in the parental control settings. If the rating system is disabled by default, enable it.
+ If the \"Fake\" rating is already blocked, unblock it, save, and then block again.
</string>
<string name="tv_parental_control_test_verify_receive_broadcast2">
TV input service must have received ACTION_BLOCKED_RATINGS_CHANGED broadcast.
</string>
<string name="tv_parental_control_test_block_unblock">
- Press the \"Launch Live Channels\" button; verify that the channel is blocked visually.
- Try unblock the screen by entering PIN; verify that it\'s unblocked visually.
+ Select the \"Launch TV app\" button; verify that the channel is blocked.
+ Try to unblock the screen by entering PIN; verify that it\'s unblocked.
</string>
- <string name="tv_launch_tv_app">Launch Live Channels</string>
+ <string name="tv_launch_tv_app">Launch TV app</string>
<string name="tv_launch_epg">Launch EPG</string>
<string name="tv_channel_not_found">
CtsVerifier channel is not set up. Please set up before proceeding.
</string>
- <string name="tv_multiple_tracks_test">Live Channels app closed captions and multi-audio test</string>
+ <string name="tv_multiple_tracks_test">TV app closed captions and multi-audio test</string>
<string name="tv_multiple_tracks_test_info">
- This test verifies that the default Live Channels app invokes proper mulitple tracks APIs in the
- framework.
+ Verify that the bundled TV app calls the multi-track API.
</string>
<string name="tv_multiple_tracks_test_select_subtitle">
- Press the \"Launch Live Channels\" button. Verify that the closed caption is off by default.
- Set closed caption to English.
+ Select the \"Launch TV app\" button. Verify that closed captions are off by default. Set closed
+ caption language to English.
</string>
<string name="tv_multiple_tracks_test_verify_set_caption_enabled">
- Caption should be enabled.
+ Captions are enabled.
</string>
<string name="tv_multiple_tracks_test_verify_select_subtitle">
- The English subtitle track should be selected.
+ The English closed caption track should be selected.
</string>
<string name="tv_multiple_tracks_test_select_audio">
- Press the \"Launch Live Channels\" button. Verify that the audio track is English by default.
+ Select the \"Launch TV app\" button. Verify that the audio track is English by default.
Select Spanish audio track.
</string>
<string name="tv_multiple_tracks_test_verify_select_audio">
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
index d325b65..9c5b31d 100755
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
@@ -236,8 +236,15 @@
public void onSurfaceTextureAvailable(SurfaceTexture surface,
int width, int height) {
mPreviewTexture = surface;
- mPreviewTexWidth = width;
- mPreviewTexHeight = height;
+ if (mFormatView.getMeasuredWidth() != width
+ || mFormatView.getMeasuredHeight() != height) {
+ mPreviewTexWidth = mFormatView.getMeasuredWidth();
+ mPreviewTexHeight = mFormatView.getMeasuredHeight();
+ } else {
+ mPreviewTexWidth = width;
+ mPreviewTexHeight = height;
+ }
+
if (mCamera != null) {
startPreview();
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/LlcpVersionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/LlcpVersionActivity.java
new file mode 100644
index 0000000..ce5a3d4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/LlcpVersionActivity.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcEvent;
+import android.nfc.NfcManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import java.nio.charset.Charset;
+
+/**
+ * Test activity that sends a particular NDEF Push message to another NFC device.
+ */
+public class LlcpVersionActivity extends PassFailButtons.Activity implements
+ NfcAdapter.CreateNdefMessageCallback {
+
+ private static final int NFC_NOT_ENABLED_DIALOG_ID = 1;
+ private static final int NDEF_PUSH_NOT_ENABLED_DIALOG_ID = 2;
+
+ private NfcAdapter mNfcAdapter;
+ private TextView mTextView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pass_fail_text);
+ setInfoResources(R.string.nfc_llcp_version_check, R.string.nfc_llcp_version_check_info, 0);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+
+ mTextView = (TextView) findViewById(R.id.text);
+ mTextView.setText(R.string.nfc_llcp_version_check_info);
+
+ NfcManager nfcManager = (NfcManager) getSystemService(NFC_SERVICE);
+ mNfcAdapter = nfcManager.getDefaultAdapter();
+ }
+
+ private static NdefMessage getTestMessage() {
+ byte[] mimeBytes = "application/com.android.cts.verifier.nfc"
+ .getBytes(Charset.forName("US-ASCII"));
+ byte[] id = new byte[] {1, 3, 3, 7};
+ byte[] payload = "CTS Verifier NDEF Push Tag".getBytes(Charset.forName("US-ASCII"));
+ return new NdefMessage(new NdefRecord[] {
+ new NdefRecord(NdefRecord.TNF_MIME_MEDIA, mimeBytes, id, payload)
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (!mNfcAdapter.isEnabled()) {
+ showDialog(NFC_NOT_ENABLED_DIALOG_ID);
+ } else if (!mNfcAdapter.isNdefPushEnabled()) {
+ /* Sender must have NDEF push enabled */
+ showDialog(NDEF_PUSH_NOT_ENABLED_DIALOG_ID);
+ }
+
+ mNfcAdapter.setNdefPushMessageCallback(this, this);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ public Dialog onCreateDialog(int id, Bundle args) {
+ switch (id) {
+ case NFC_NOT_ENABLED_DIALOG_ID:
+ return NfcDialogs.createNotEnabledDialog(this);
+ case NDEF_PUSH_NOT_ENABLED_DIALOG_ID:
+ return NfcDialogs.createNdefPushNotEnabledDialog(this);
+ default:
+ return super.onCreateDialog(id, args);
+ }
+ }
+
+ @Override
+ public NdefMessage createNdefMessage(NfcEvent event) {
+ if (event.peerLlcpMajorVersion <= 1 && event.peerLlcpMinorVersion < 2) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTextView.setText(R.string.nfc_llcp_version_check_failure);
+ }
+ });
+ } else {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getPassButton().setEnabled(true);
+ mTextView.setText(R.string.nfc_llcp_version_check_success);
+ }
+ });
+ }
+ return null;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushSenderActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushSenderActivity.java
index f3f37c4..2f77895 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushSenderActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushSenderActivity.java
@@ -23,6 +23,7 @@
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
+import android.nfc.NfcEvent;
import android.nfc.NfcManager;
import android.os.Bundle;
import android.widget.TextView;
@@ -32,7 +33,8 @@
/**
* Test activity that sends a particular NDEF Push message to another NFC device.
*/
-public class NdefPushSenderActivity extends PassFailButtons.Activity {
+public class NdefPushSenderActivity extends PassFailButtons.Activity implements
+ NfcAdapter.CreateNdefMessageCallback {
static final NdefMessage TEST_MESSAGE = getTestMessage();
@@ -76,13 +78,12 @@
showDialog(NDEF_PUSH_NOT_ENABLED_DIALOG_ID);
}
- mNfcAdapter.enableForegroundNdefPush(this, TEST_MESSAGE);
+ mNfcAdapter.setNdefPushMessageCallback(this, this);
}
@Override
protected void onPause() {
super.onPause();
- mNfcAdapter.disableForegroundNdefPush(this);
}
@Override
@@ -96,4 +97,9 @@
return super.onCreateDialog(id, args);
}
}
+
+ @Override
+ public NdefMessage createNdefMessage(NfcEvent event) {
+ return getTestMessage();
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java
index cb90241..68fc027 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java
@@ -28,6 +28,7 @@
import android.nfc.tech.MifareUltralight;
import android.nfc.tech.Ndef;
import android.nfc.tech.TagTechnology;
+import android.os.Build;
import android.os.Bundle;
/** Activity that lists all the NFC tests. */
@@ -58,6 +59,11 @@
NdefPushReceiverActivity.class.getName(),
new Intent(this, NdefPushReceiverActivity.class), null));
+ if ("MNC".equals(Build.VERSION.CODENAME) || Build.VERSION.SDK_INT >= 23) {
+ adapter.add(TestListItem.newTest(this, R.string.nfc_llcp_version_check,
+ LlcpVersionActivity.class.getName(),
+ new Intent(this, LlcpVersionActivity.class), null));
+ }
adapter.add(TestListItem.newCategory(this, R.string.nfc_tag_verification));
adapter.add(TestListItem.newTest(this, R.string.nfc_ndef,
NDEF_ID, getTagIntent(Ndef.class), null));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
index 035ce86..879916b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
@@ -40,12 +40,6 @@
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
adapter.add(TestListItem.newCategory(this, R.string.nfc_hce_reader_tests));
- /*
- * Only add this test when supported in platform
- adapter.add(TestListItem.newTest(this, R.string.nfc_hce_default_route_reader,
- SimpleReaderActivity.class.getName(),
- DefaultRouteEmulatorActivity.buildReaderIntent(this), null));
- */
adapter.add(TestListItem.newTest(this, R.string.nfc_hce_protocol_params_reader,
ProtocolParamsReaderActivity.class.getName(),
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
new file mode 100644
index 0000000..70899c6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.verifier.security;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.Manifest;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.util.Log;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.UserNotAuthenticatedException;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+public class FingerprintBoundKeysTest extends PassFailButtons.Activity {
+ private static final String TAG = "FingerprintBoundKeysTest";
+
+ /** Alias for our key in the Android Key Store. */
+ private static final String KEY_NAME = "my_key";
+ private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6};
+ private static final int AUTHENTICATION_DURATION_SECONDS = 5;
+ private static final int CONFIRM_CREDENTIALS_REQUEST_CODE = 1;
+ private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0;
+
+ private FingerprintManager mFingerprintManager;
+ private KeyguardManager mKeyguardManager;
+ private FingerprintAuthDialogFragment mFingerprintDialog;
+ private Cipher mCipher;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.sec_screen_lock_keys_main);
+ setPassFailButtonClickListeners();
+ setInfoResources(R.string.sec_fingerprint_bound_key_test, R.string.sec_fingerprint_bound_key_test_info, -1);
+ getPassButton().setEnabled(false);
+ requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT},
+ FINGERPRINT_PERMISSION_REQUEST_CODE);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
+ if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE && state[0] == PackageManager.PERMISSION_GRANTED) {
+ mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
+ mKeyguardManager = (KeyguardManager) getSystemService(KeyguardManager.class);
+
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
+ mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ + KeyProperties.BLOCK_MODE_CBC + "/"
+ + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+
+ mCipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ } catch (KeyPermanentlyInvalidatedException e) {
+ createKey();
+ showToast("The key has been invalidated, please try again.\n");
+ } catch (NoSuchPaddingException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
+ | NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException("Failed to init Cipher", e);
+ }
+
+ Button startTestButton = (Button) findViewById(R.id.sec_start_test_button);
+ startTestButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (tryEncrypt()) {
+ showToast("Test failed. Key accessible without auth.");
+ } else {
+ showAuthenticationScreen();
+ }
+ }
+
+ });
+
+ if (!mKeyguardManager.isKeyguardSecure()) {
+ // Show a message that the user hasn't set up a lock screen.
+ showToast( "Secure lock screen hasn't been set up.\n"
+ + "Go to 'Settings -> Security -> Screen lock' to set up a lock screen");
+ startTestButton.setEnabled(false);
+ } else if (!mFingerprintManager.hasEnrolledFingerprints()) {
+ showToast("No fingerprints enrolled.\n"
+ + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint");
+ startTestButton.setEnabled(false);
+ } else {
+ createKey();
+ }
+ }
+ }
+
+ /**
+ * Creates a symmetric key in the Android Key Store which can only be used after the user has
+ * authenticated with device credentials within the last X seconds.
+ */
+ private void createKey() {
+ // Generate a key to decrypt payment credentials, tokens, etc.
+ // This will most likely be a registration step for the user when they are setting up your app.
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+
+ // Set the alias of the entry in Android KeyStore where the key will appear
+ // and the constrains (purposes) in the constructor of the Builder
+ keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+ .setUserAuthenticationRequired(true)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+ .build());
+ keyGenerator.generateKey();
+ } catch (NoSuchAlgorithmException | NoSuchProviderException
+ | InvalidAlgorithmParameterException | KeyStoreException
+ | CertificateException | IOException e) {
+ throw new RuntimeException("Failed to create a symmetric key", e);
+ }
+ }
+
+ /**
+ * Tries to encrypt some data with the generated key in {@link #createKey} which is
+ * only works if the user has just authenticated via device credentials.
+ */
+ private boolean tryEncrypt() {
+ try {
+ mCipher.doFinal(SECRET_BYTE_ARRAY);
+ return true;
+ } catch (BadPaddingException | IllegalBlockSizeException e) {
+ return false;
+ }
+ }
+
+ private void showAuthenticationScreen() {
+ mFingerprintDialog = new FingerprintAuthDialogFragment();
+ mFingerprintDialog.show(getFragmentManager(), "fingerprint_dialog");
+ }
+
+ private void showToast(String message) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG)
+ .show();
+ }
+
+ public class FingerprintAuthDialogFragment extends DialogFragment {
+
+ private CancellationSignal mCancellationSignal;
+ private FingerprintManagerCallback mFingerprintManagerCallback;
+ private boolean mSelfCancelled;
+
+ class FingerprintManagerCallback extends FingerprintManager.AuthenticationCallback {
+ @Override
+ public void onAuthenticationError(int errMsgId, CharSequence errString) {
+ if (!mSelfCancelled) {
+ showToast(errString.toString());
+ }
+ }
+
+ @Override
+ public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+ showToast(helpString.toString());
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ showToast(getString(R.string.sec_fp_auth_failed));
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+ if (tryEncrypt()) {
+ showToast("Test passed.");
+ getPassButton().setEnabled(true);
+ FingerprintAuthDialogFragment.this.dismiss();
+ } else {
+ showToast("Test failed. Key not accessible after auth");
+ }
+ }
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mCancellationSignal.cancel();
+ mSelfCancelled = true;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mCancellationSignal = new CancellationSignal();
+ mSelfCancelled = false;
+ mFingerprintManagerCallback = new FingerprintManagerCallback();
+ mFingerprintManager.authenticate(new FingerprintManager.CryptoObject(mCipher),
+ mCancellationSignal, 0, mFingerprintManagerCallback, null);
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage(R.string.sec_fp_dialog_message);
+ return builder.create();
+ }
+
+ }
+}
+
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/ScreenLockBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/ScreenLockBoundKeysTest.java
new file mode 100644
index 0000000..618b99a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/ScreenLockBoundKeysTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.security;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.UserNotAuthenticatedException;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+public class ScreenLockBoundKeysTest extends PassFailButtons.Activity {
+
+ /** Alias for our key in the Android Key Store. */
+ private static final String KEY_NAME = "my_key";
+ private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6};
+ private static final int AUTHENTICATION_DURATION_SECONDS = 5;
+ private static final int CONFIRM_CREDENTIALS_REQUEST_CODE = 1;
+
+ private KeyguardManager mKeyguardManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.sec_screen_lock_keys_main);
+ setPassFailButtonClickListeners();
+ setInfoResources(R.string.sec_lock_bound_key_test, R.string.sec_lock_bound_key_test_info, -1);
+
+ mKeyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
+
+ getPassButton().setEnabled(false);
+
+ Button startTestButton = (Button) findViewById(R.id.sec_start_test_button);
+ startTestButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showToast("Test running...");
+ v.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (tryEncrypt()) {
+ showToast("Test failed. Key accessible without auth.");
+ } else {
+ showAuthenticationScreen();
+ }
+ }
+ },
+ AUTHENTICATION_DURATION_SECONDS * 1000);
+ }
+
+ });
+
+ if (!mKeyguardManager.isKeyguardSecure()) {
+ // Show a message that the user hasn't set up a lock screen.
+ Toast.makeText(this,
+ "Secure lock screen hasn't set up.\n"
+ + "Go to 'Settings -> Security -> Screenlock' to set up a lock screen",
+ Toast.LENGTH_LONG).show();
+ startTestButton.setEnabled(false);
+ return;
+ }
+
+ createKey();
+ }
+
+ /**
+ * Creates a symmetric key in the Android Key Store which can only be used after the user has
+ * authenticated with device credentials within the last X seconds.
+ */
+ private void createKey() {
+ // Generate a key to decrypt payment credentials, tokens, etc.
+ // This will most likely be a registration step for the user when they are setting up your app.
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+
+ // Set the alias of the entry in Android KeyStore where the key will appear
+ // and the constrains (purposes) in the constructor of the Builder
+ keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+ .setUserAuthenticationRequired(true)
+ // Require that the user has unlocked in the last 30 seconds
+ .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+ .build());
+ keyGenerator.generateKey();
+ } catch (NoSuchAlgorithmException | NoSuchProviderException
+ | InvalidAlgorithmParameterException | KeyStoreException
+ | CertificateException | IOException e) {
+ throw new RuntimeException("Failed to create a symmetric key", e);
+ }
+ }
+
+ /**
+ * Tries to encrypt some data with the generated key in {@link #createKey} which is
+ * only works if the user has just authenticated via device credentials.
+ */
+ private boolean tryEncrypt() {
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
+ Cipher cipher = Cipher.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
+ + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+
+ // Try encrypting something, it will only work if the user authenticated within
+ // the last AUTHENTICATION_DURATION_SECONDS seconds.
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ cipher.doFinal(SECRET_BYTE_ARRAY);
+ return true;
+ } catch (UserNotAuthenticatedException e) {
+ // User is not authenticated, let's authenticate with device credentials.
+ return false;
+ } catch (KeyPermanentlyInvalidatedException e) {
+ // This happens if the lock screen has been disabled or reset after the key was
+ // generated after the key was generated.
+ createKey();
+ Toast.makeText(this, "Set up lockscreen after test ran. Retry the test.\n"
+ + e.getMessage(),
+ Toast.LENGTH_LONG).show();
+ return false;
+ } catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException |
+ CertificateException | UnrecoverableKeyException | IOException
+ | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void showAuthenticationScreen() {
+ // Create the Confirm Credentials screen. You can customize the title and description. Or
+ // we will provide a generic one for you if you leave it null
+ Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
+ if (intent != null) {
+ startActivityForResult(intent, CONFIRM_CREDENTIALS_REQUEST_CODE);
+ }
+ }
+
+ private void showToast(String message) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG)
+ .show();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case CONFIRM_CREDENTIALS_REQUEST_CODE:
+ if (resultCode == RESULT_OK) {
+ if (tryEncrypt()) {
+ showToast("Test passed.");
+ getPassButton().setEnabled(true);
+ } else {
+ showToast("Test failed. Key not accessible after auth");
+ }
+ }
+ }
+ }
+}
+
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java b/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java
index 1cea80c..d01978a 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java
@@ -28,11 +28,11 @@
* A simple activity to create and manage wifi configurations.
*/
public class WifiConfigCreator {
- public static final String CREATE_WIFI_CONFIG_ACTION =
+ public static final String ACTION_CREATE_WIFI_CONFIG =
"com.android.compatibility.common.util.CREATE_WIFI_CONFIG";
- public static final String UPDATE_WIFI_CONFIG_ACTION =
+ public static final String ACTION_UPDATE_WIFI_CONFIG =
"com.android.compatibility.common.util.UPDATE_WIFI_CONFIG";
- public static final String REMOVE_WIFI_CONFIG_ACTION =
+ public static final String ACTION_REMOVE_WIFI_CONFIG =
"com.android.compatibility.common.util.REMOVE_WIFI_CONFIG";
public static final String EXTRA_NETID = "extra-netid";
public static final String EXTRA_SSID = "extra-ssid";
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AdoptableHostTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AdoptableHostTest.java
new file mode 100644
index 0000000..e256f76
--- /dev/null
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AdoptableHostTest.java
@@ -0,0 +1,350 @@
+/*
+ * 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 static com.android.cts.appsecurity.SplitTests.ABI_TO_APK;
+import static com.android.cts.appsecurity.SplitTests.APK;
+import static com.android.cts.appsecurity.SplitTests.APK_mdpi;
+import static com.android.cts.appsecurity.SplitTests.APK_xxhdpi;
+import static com.android.cts.appsecurity.SplitTests.CLASS;
+import static com.android.cts.appsecurity.SplitTests.PKG;
+
+import com.android.cts.appsecurity.SplitTests.BaseInstallMultiple;
+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;
+
+import java.util.Arrays;
+
+/**
+ * Set of tests that verify behavior of adopted storage media, if supported.
+ */
+public class AdoptableHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+ 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 testApps() throws Exception {
+ if (!hasAdoptable()) return;
+ final String diskId = getAdoptionDisk();
+ try {
+ final String abi = mAbi.getName();
+ final String apk = ABI_TO_APK.get(abi);
+ assertNotNull("Failed to find APK for ABI " + abi, apk);
+
+ // Install simple app on internal
+ new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
+ runDeviceTests(PKG, CLASS, "testDataInternal");
+ runDeviceTests(PKG, CLASS, "testDataWrite");
+ runDeviceTests(PKG, CLASS, "testDataRead");
+ runDeviceTests(PKG, CLASS, "testNative");
+
+ // Adopt that disk!
+ assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+ final LocalVolumeInfo vol = getAdoptionVolume();
+
+ // Move app and verify
+ assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " " + vol.uuid));
+ runDeviceTests(PKG, CLASS, "testDataNotInternal");
+ runDeviceTests(PKG, CLASS, "testDataRead");
+ runDeviceTests(PKG, CLASS, "testNative");
+
+ // Unmount, remount and verify
+ getDevice().executeShellCommand("sm unmount " + vol.volId);
+ getDevice().executeShellCommand("sm mount " + vol.volId);
+ runDeviceTests(PKG, CLASS, "testDataNotInternal");
+ runDeviceTests(PKG, CLASS, "testDataRead");
+ runDeviceTests(PKG, CLASS, "testNative");
+
+ // Move app back and verify
+ assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal"));
+ runDeviceTests(PKG, CLASS, "testDataInternal");
+ runDeviceTests(PKG, CLASS, "testDataRead");
+ runDeviceTests(PKG, CLASS, "testNative");
+
+ // Un-adopt volume and app should still be fine
+ getDevice().executeShellCommand("sm partition " + diskId + " public");
+ runDeviceTests(PKG, CLASS, "testDataInternal");
+ runDeviceTests(PKG, CLASS, "testDataRead");
+ runDeviceTests(PKG, CLASS, "testNative");
+
+ } finally {
+ cleanUp(diskId);
+ }
+ }
+
+ public void testPrimaryStorage() throws Exception {
+ if (!hasAdoptable()) return;
+ final String diskId = getAdoptionDisk();
+ try {
+ final String originalVol = getDevice()
+ .executeShellCommand("sm get-primary-storage-uuid").trim();
+
+ if ("null".equals(originalVol)) {
+ verifyPrimaryInternal(diskId);
+ } else if ("primary_physical".equals(originalVol)) {
+ verifyPrimaryPhysical(diskId);
+ }
+ } finally {
+ cleanUp(diskId);
+ }
+ }
+
+ private void verifyPrimaryInternal(String diskId) throws Exception {
+ // Write some data to shared storage
+ new InstallMultiple().addApk(APK).run();
+ runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
+ runDeviceTests(PKG, CLASS, "testPrimaryInternal");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+ // Adopt that disk!
+ assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+ final LocalVolumeInfo vol = getAdoptionVolume();
+
+ // Move storage there and verify that data went along for ride
+ assertSuccess(getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid));
+ runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+ // Unmount and verify
+ getDevice().executeShellCommand("sm unmount " + vol.volId);
+ runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
+ getDevice().executeShellCommand("sm mount " + vol.volId);
+ runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+ // Move app and verify backing storage volume is same
+ assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " " + vol.uuid));
+ runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+ // And move back to internal
+ assertSuccess(getDevice().executeShellCommand("pm move-primary-storage internal"));
+ runDeviceTests(PKG, CLASS, "testPrimaryInternal");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+ assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal"));
+ runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ }
+
+ private void verifyPrimaryPhysical(String diskId) throws Exception {
+ // Write some data to shared storage
+ new InstallMultiple().addApk(APK).run();
+ runDeviceTests(PKG, CLASS, "testPrimaryPhysical");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+ // Adopt that disk!
+ assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+ final LocalVolumeInfo vol = getAdoptionVolume();
+
+ // Move primary storage there, but since we just nuked primary physical
+ // the storage device will be empty
+ assertSuccess(getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid));
+ runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+ // Unmount and verify
+ getDevice().executeShellCommand("sm unmount " + vol.volId);
+ runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
+ getDevice().executeShellCommand("sm mount " + vol.volId);
+ runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+ // And move to internal
+ assertSuccess(getDevice().executeShellCommand("pm move-primary-storage internal"));
+ runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
+ runDeviceTests(PKG, CLASS, "testPrimaryInternal");
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ }
+
+ /**
+ * Verify that we can install both new and inherited packages directly on
+ * adopted volumes.
+ */
+ public void testPackageInstaller() throws Exception {
+ if (!hasAdoptable()) return;
+ final String diskId = getAdoptionDisk();
+ try {
+ assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+ final LocalVolumeInfo vol = getAdoptionVolume();
+
+ // Install directly onto adopted volume
+ new InstallMultiple().locationAuto().forceUuid(vol.uuid)
+ .addApk(APK).addApk(APK_mdpi).run();
+ runDeviceTests(PKG, CLASS, "testDataNotInternal");
+ runDeviceTests(PKG, CLASS, "testDensityBest1");
+
+ // Now splice in an additional split which offers better resources
+ new InstallMultiple().locationAuto().inheritFrom(PKG)
+ .addApk(APK_xxhdpi).run();
+ runDeviceTests(PKG, CLASS, "testDataNotInternal");
+ runDeviceTests(PKG, CLASS, "testDensityBest2");
+
+ } finally {
+ cleanUp(diskId);
+ }
+ }
+
+ /**
+ * Verify behavior when changes occur while adopted device is ejected and
+ * returned at a later time.
+ */
+ public void testEjected() throws Exception {
+ if (!hasAdoptable()) return;
+ final String diskId = getAdoptionDisk();
+ try {
+ assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+ final LocalVolumeInfo vol = getAdoptionVolume();
+
+ // Install directly onto adopted volume, and write data there
+ new InstallMultiple().locationAuto().forceUuid(vol.uuid).addApk(APK).run();
+ runDeviceTests(PKG, CLASS, "testDataNotInternal");
+ runDeviceTests(PKG, CLASS, "testDataWrite");
+ runDeviceTests(PKG, CLASS, "testDataRead");
+
+ // Now unmount and uninstall; leaving stale package on adopted volume
+ getDevice().executeShellCommand("sm unmount " + vol.volId);
+ getDevice().uninstallPackage(PKG);
+
+ // Install second copy on internal, but don't write anything
+ new InstallMultiple().locationInternalOnly().addApk(APK).run();
+ runDeviceTests(PKG, CLASS, "testDataInternal");
+
+ // Kick through a remount cycle, which should purge the adopted app
+ getDevice().executeShellCommand("sm mount " + vol.volId);
+ runDeviceTests(PKG, CLASS, "testDataInternal");
+ try {
+ runDeviceTests(PKG, CLASS, "testDataRead");
+ fail("Unexpected data from adopted volume picked up");
+ } catch (AssertionError expected) {
+ }
+ getDevice().executeShellCommand("sm unmount " + vol.volId);
+
+ // Uninstall the internal copy and remount; we should have no record of app
+ getDevice().uninstallPackage(PKG);
+ getDevice().executeShellCommand("sm mount " + vol.volId);
+
+ try {
+ runDeviceTests(PKG, CLASS, "testNothing");
+ fail("Unexpected app not purged from adopted volume");
+ } catch (AssertionError expected) {
+ }
+ } finally {
+ cleanUp(diskId);
+ }
+ }
+
+ private boolean hasAdoptable() throws Exception {
+ return Boolean.parseBoolean(getDevice().executeShellCommand("sm has-adoptable").trim());
+ }
+
+ private String getAdoptionDisk() throws Exception {
+ final String disks = getDevice().executeShellCommand("sm list-disks adoptable");
+ if (disks == null || disks.length() == 0) {
+ throw new AssertionError("Devices that claim to support adoptable storage must have "
+ + "adoptable media inserted during CTS to verify correct behavior");
+ }
+ return disks.split("\n")[0].trim();
+ }
+
+ private LocalVolumeInfo getAdoptionVolume() throws Exception {
+ final String[] lines = getDevice().executeShellCommand("sm list-volumes private")
+ .split("\n");
+ for (String line : lines) {
+ final LocalVolumeInfo info = new LocalVolumeInfo(line.trim());
+ if (!"private".equals(info.volId)) {
+ return info;
+ }
+ }
+ throw new AssertionError("Expected private volume; found " + Arrays.toString(lines));
+ }
+
+ private void cleanUp(String diskId) throws Exception {
+ getDevice().executeShellCommand("sm partition " + diskId + " public");
+ getDevice().executeShellCommand("sm forget all");
+ }
+
+ private void runDeviceTests(String packageName, String testClassName, String testMethodName)
+ throws DeviceNotAvailableException {
+ Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+ }
+
+ private static void assertSuccess(String str) {
+ if (str == null || !str.startsWith("Success")) {
+ throw new AssertionError("Expected success string but found " + str);
+ }
+ }
+
+ private static void assertEmpty(String str) {
+ if (str != null && str.trim().length() > 0) {
+ throw new AssertionError("Expected empty string but found " + str);
+ }
+ }
+
+ private static class LocalVolumeInfo {
+ public String volId;
+ public String state;
+ public String uuid;
+
+ public LocalVolumeInfo(String line) {
+ final String[] split = line.split(" ");
+ volId = split[0];
+ state = split[1];
+ uuid = split[2];
+ }
+ }
+
+ private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
+ public InstallMultiple() {
+ super(getDevice(), mCtsBuild, mAbi);
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
index 206bdbe..bf9e81c 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
@@ -19,15 +19,8 @@
import com.android.cts.tradefed.build.CtsBuildHelper;
import com.android.cts.util.AbiUtils;
import com.android.ddmlib.Log;
-import com.android.ddmlib.testrunner.InstrumentationResultParser;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.result.CollectingTestListener;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
@@ -35,15 +28,13 @@
import java.io.File;
import java.io.FileNotFoundException;
-import java.util.Map;
/**
- * Set of tests that verify various security checks involving multiple apps are properly enforced.
+ * Set of tests that verify various security checks involving multiple apps are
+ * properly enforced.
*/
public class AppSecurityTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
- private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
-
// testSharedUidDifferentCerts constants
private static final String SHARED_UI_APK = "CtsSharedUidInstall.apk";
private static final String SHARED_UI_PKG = "com.android.cts.shareuidinstall";
@@ -68,18 +59,6 @@
private static final String APP_ACCESS_DATA_APK = "CtsAppAccessData.apk";
private static final String APP_ACCESS_DATA_PKG = "com.android.cts.appaccessdata";
- // External storage constants
- private static final String COMMON_EXTERNAL_STORAGE_APP_CLASS = "com.android.cts.externalstorageapp.CommonExternalStorageTest";
- private static final String EXTERNAL_STORAGE_APP_APK = "CtsExternalStorageApp.apk";
- private static final String EXTERNAL_STORAGE_APP_PKG = "com.android.cts.externalstorageapp";
- private static final String EXTERNAL_STORAGE_APP_CLASS = EXTERNAL_STORAGE_APP_PKG + ".ExternalStorageTest";
- private static final String READ_EXTERNAL_STORAGE_APP_APK = "CtsReadExternalStorageApp.apk";
- private static final String READ_EXTERNAL_STORAGE_APP_PKG = "com.android.cts.readexternalstorageapp";
- private static final String READ_EXTERNAL_STORAGE_APP_CLASS = READ_EXTERNAL_STORAGE_APP_PKG + ".ReadExternalStorageTest";
- private static final String WRITE_EXTERNAL_STORAGE_APP_APK = "CtsWriteExternalStorageApp.apk";
- private static final String WRITE_EXTERNAL_STORAGE_APP_PKG = "com.android.cts.writeexternalstorageapp";
- private static final String WRITE_EXTERNAL_STORAGE_APP_CLASS = WRITE_EXTERNAL_STORAGE_APP_PKG + ".WriteExternalStorageTest";
-
// testInstrumentationDiffCert constants
private static final String TARGET_INSTRUMENT_APK = "CtsTargetInstrumentationApp.apk";
private static final String TARGET_INSTRUMENT_PKG = "com.android.cts.targetinstrumentationapp";
@@ -97,17 +76,8 @@
private static final String PERMISSION_DIFF_CERT_PKG =
"com.android.cts.usespermissiondiffcertapp";
- private static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
-
- private static final String MULTIUSER_STORAGE_APK = "CtsMultiUserStorageApp.apk";
- private static final String MULTIUSER_STORAGE_PKG = "com.android.cts.multiuserstorageapp";
- private static final String MULTIUSER_STORAGE_CLASS = MULTIUSER_STORAGE_PKG
- + ".MultiUserStorageTest";
-
private static final String LOG_TAG = "AppSecurityTests";
- private static final int USER_OWNER = 0;
-
private IAbi mAbi;
private CtsBuildHelper mCtsBuild;
@@ -156,8 +126,7 @@
assertNotNull("shared uid app with different cert than existing app installed " +
"successfully", installResult);
assertEquals("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE", installResult);
- }
- finally {
+ } finally {
getDevice().uninstallPackage(SHARED_UI_PKG);
getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG);
}
@@ -183,8 +152,7 @@
assertNotNull("app upgrade with different cert than existing app installed " +
"successfully", installResult);
assertEquals("INSTALL_FAILED_UPDATE_INCOMPATIBLE", installResult);
- }
- finally {
+ } finally {
getDevice().uninstallPackage(SIMPLE_APP_PKG);
}
}
@@ -205,132 +173,21 @@
assertNull(String.format("failed to install app with data. Reason: %s", installResult),
installResult);
// run appwithdata's tests to create private data
- assertTrue("failed to create app's private data", runDeviceTests(APP_WITH_DATA_PKG,
- APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD));
+ runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
installResult = getDevice().installPackage(getTestAppFile(APP_ACCESS_DATA_APK),
false, options);
assertNull(String.format("failed to install app access data. Reason: %s",
installResult), installResult);
// run appaccessdata's tests which attempt to access appwithdata's private data
- assertTrue("could access app's private data", runDeviceTests(APP_ACCESS_DATA_PKG));
- }
- finally {
+ runDeviceTests(APP_ACCESS_DATA_PKG);
+ } finally {
getDevice().uninstallPackage(APP_WITH_DATA_PKG);
getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
}
}
/**
- * Verify that app with no external storage permissions works correctly.
- */
- public void testExternalStorageNone() throws Exception {
- final int[] users = createUsersForTest();
- try {
- wipePrimaryExternalStorage(getDevice());
-
- getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
- assertNull(getDevice()
- .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false, options));
-
- for (int user : users) {
- assertTrue("Failed external storage with no permissions",
- runDeviceTests(EXTERNAL_STORAGE_APP_PKG, user));
- }
- } finally {
- getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
- removeUsersForTest(users);
- }
- }
-
- /**
- * Verify that app with
- * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} works
- * correctly.
- */
- public void testExternalStorageRead() throws Exception {
- final int[] users = createUsersForTest();
- try {
- wipePrimaryExternalStorage(getDevice());
-
- getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
- assertNull(getDevice()
- .installPackage(getTestAppFile(READ_EXTERNAL_STORAGE_APP_APK), false, options));
-
- for (int user : users) {
- assertTrue("Failed external storage with read permissions",
- runDeviceTests(READ_EXTERNAL_STORAGE_APP_PKG, user));
- }
- } finally {
- getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
- removeUsersForTest(users);
- }
- }
-
- /**
- * Verify that app with
- * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} works
- * correctly.
- */
- public void testExternalStorageWrite() throws Exception {
- final int[] users = createUsersForTest();
- try {
- wipePrimaryExternalStorage(getDevice());
-
- getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
- assertNull(getDevice()
- .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false, options));
-
- for (int user : users) {
- assertTrue("Failed external storage with write permissions",
- runDeviceTests(WRITE_EXTERNAL_STORAGE_APP_PKG, user));
- }
- } finally {
- getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
- removeUsersForTest(users);
- }
- }
-
- /**
- * Verify that app with WRITE_EXTERNAL can leave gifts in external storage
- * directories belonging to other apps, and those apps can read.
- */
- public void testExternalStorageGifts() throws Exception {
- final int[] users = createUsersForTest();
- try {
- wipePrimaryExternalStorage(getDevice());
-
- getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
- getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
- getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
- assertNull(getDevice()
- .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false, options));
- assertNull(getDevice()
- .installPackage(getTestAppFile(READ_EXTERNAL_STORAGE_APP_APK), false, options));
- assertNull(getDevice()
- .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false, options));
-
- for (int user : users) {
- assertTrue("Failed to write gifts", runDeviceTests(WRITE_EXTERNAL_STORAGE_APP_PKG,
- WRITE_EXTERNAL_STORAGE_APP_CLASS, "doWriteGifts", user));
- assertTrue("Read failed to verify gifts", runDeviceTests(READ_EXTERNAL_STORAGE_APP_PKG,
- READ_EXTERNAL_STORAGE_APP_CLASS, "doVerifyGifts", user));
- assertTrue("None failed to verify gifts", runDeviceTests(EXTERNAL_STORAGE_APP_PKG,
- EXTERNAL_STORAGE_APP_CLASS, "doVerifyGifts", user));
- }
- } finally {
- getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
- getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
- getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
- removeUsersForTest(users);
- }
- }
-
- /**
* Test that uninstall of an app removes its private data.
*/
public void testUninstallRemovesData() throws Exception {
@@ -345,8 +202,7 @@
assertNull(String.format("failed to install app with data. Reason: %s", installResult),
installResult);
// run appwithdata's tests to create private data
- assertTrue("failed to create app's private data", runDeviceTests(APP_WITH_DATA_PKG,
- APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD));
+ runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
getDevice().uninstallPackage(APP_WITH_DATA_PKG);
@@ -355,11 +211,9 @@
assertNull(String.format("failed to install app with data second time. Reason: %s",
installResult), installResult);
// run appwithdata's 'check if file exists' test
- assertTrue("app's private data still exists after install", runDeviceTests(
- APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CHECK_NOEXIST_METHOD));
-
- }
- finally {
+ runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS,
+ APP_WITH_DATA_CHECK_NOEXIST_METHOD);
+ } finally {
getDevice().uninstallPackage(APP_WITH_DATA_PKG);
}
}
@@ -389,10 +243,8 @@
// run INSTRUMENT_DIFF_CERT_PKG tests
// this test will attempt to call startInstrumentation directly and verify
// SecurityException is thrown
- assertTrue("running instrumentation with diff cert unexpectedly succeeded",
- runDeviceTests(INSTRUMENT_DIFF_CERT_PKG));
- }
- finally {
+ runDeviceTests(INSTRUMENT_DIFF_CERT_PKG);
+ } finally {
getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
}
@@ -427,209 +279,20 @@
assertNull(String.format("failed to install permission app with diff cert. Reason: %s",
installResult), installResult);
// run PERMISSION_DIFF_CERT_PKG tests which try to access the permission
- TestRunResult result = doRunTests(PERMISSION_DIFF_CERT_PKG, null, null, USER_OWNER);
- assertDeviceTestsPass(result);
- }
- finally {
+ runDeviceTests(PERMISSION_DIFF_CERT_PKG);
+ } finally {
getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
}
}
- /**
- * Test multi-user emulated storage environment, ensuring that each user has
- * isolated storage.
- */
- public void testMultiUserStorageIsolated() throws Exception {
- final String PACKAGE = MULTIUSER_STORAGE_PKG;
- final String CLAZZ = MULTIUSER_STORAGE_CLASS;
-
- final int[] users = createUsersForTest();
- try {
- if (users.length == 1) {
- Log.d(LOG_TAG, "Single user device; skipping isolated storage tests");
- return;
- }
-
- final int owner = users[0];
- final int secondary = users[1];
-
- // Install our test app
- getDevice().uninstallPackage(MULTIUSER_STORAGE_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
- final String installResult = getDevice()
- .installPackage(getTestAppFile(MULTIUSER_STORAGE_APK), false, options);
- assertNull("Failed to install: " + installResult, installResult);
-
- // Clear data from previous tests
- assertDeviceTestsPass(
- doRunTests(PACKAGE, CLAZZ, "cleanIsolatedStorage", owner));
- assertDeviceTestsPass(
- doRunTests(PACKAGE, CLAZZ, "cleanIsolatedStorage", secondary));
-
- // Have both users try writing into isolated storage
- assertDeviceTestsPass(
- doRunTests(PACKAGE, CLAZZ, "writeIsolatedStorage", owner));
- assertDeviceTestsPass(
- doRunTests(PACKAGE, CLAZZ, "writeIsolatedStorage", secondary));
-
- // Verify they both have isolated view of storage
- assertDeviceTestsPass(
- doRunTests(PACKAGE, CLAZZ, "readIsolatedStorage", owner));
- assertDeviceTestsPass(
- doRunTests(PACKAGE, CLAZZ, "readIsolatedStorage", secondary));
- } finally {
- getDevice().uninstallPackage(MULTIUSER_STORAGE_PKG);
- removeUsersForTest(users);
- }
+ private void runDeviceTests(String packageName) throws DeviceNotAvailableException {
+ Utils.runDeviceTests(getDevice(), packageName);
}
- /**
- * Helper method that checks that all tests in given result passed, and attempts to generate
- * a meaningful error message if they failed.
- *
- * @param result
- */
- private void assertDeviceTestsPass(TestRunResult result) {
- // TODO: consider rerunning if this occurred
- assertFalse(String.format("Failed to successfully run device tests for %s. Reason: %s",
- result.getName(), result.getRunFailureMessage()), result.isRunFailure());
-
- if (result.hasFailedTests()) {
- // build a meaningful error message
- StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
- for (Map.Entry<TestIdentifier, TestResult> resultEntry :
- result.getTestResults().entrySet()) {
- if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
- errorBuilder.append(resultEntry.getKey().toString());
- errorBuilder.append(":\n");
- errorBuilder.append(resultEntry.getValue().getStackTrace());
- }
- }
- fail(errorBuilder.toString());
- }
- }
-
- /**
- * Helper method that will the specified packages tests on device.
- *
- * @param pkgName Android application package for tests
- * @return <code>true</code> if all tests passed.
- * @throws DeviceNotAvailableException if connection to device was lost.
- */
- private boolean runDeviceTests(String pkgName) throws DeviceNotAvailableException {
- return runDeviceTests(pkgName, null, null, USER_OWNER);
- }
-
- private boolean runDeviceTests(String pkgName, int userId) throws DeviceNotAvailableException {
- return runDeviceTests(pkgName, null, null, userId);
- }
-
- /**
- * Helper method that will the specified packages tests on device.
- *
- * @param pkgName Android application package for tests
- * @return <code>true</code> if all tests passed.
- * @throws DeviceNotAvailableException if connection to device was lost.
- */
- private boolean runDeviceTests(String pkgName, String testClassName, String testMethodName)
+ private void runDeviceTests(String packageName, String testClassName, String testMethodName)
throws DeviceNotAvailableException {
- return runDeviceTests(pkgName, testClassName, testMethodName, USER_OWNER);
- }
-
- private boolean runDeviceTests(String pkgName, String testClassName, String testMethodName,
- int userId) throws DeviceNotAvailableException {
- TestRunResult runResult = doRunTests(pkgName, testClassName, testMethodName,
- userId);
- return !runResult.hasFailedTests();
- }
-
- private static boolean isMultiUserSupportedOnDevice(ITestDevice device)
- throws DeviceNotAvailableException {
- // TODO: move this to ITestDevice once it supports users
- final String output = device.executeShellCommand("pm get-max-users");
- try {
- return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim()) > 1;
- } catch (NumberFormatException e) {
- fail("Failed to parse result: " + output);
- }
- return false;
- }
-
- /**
- * Return set of users that test should be run for, creating a secondary
- * user if the device supports it. Always call
- * {@link #removeUsersForTest(int[])} when finished.
- */
- private int[] createUsersForTest() throws DeviceNotAvailableException {
- if (isMultiUserSupportedOnDevice(getDevice())) {
- return new int[] { USER_OWNER, createUserOnDevice(getDevice()) };
- } else {
- Log.d(LOG_TAG, "Single user device; skipping isolated storage tests");
- return new int[] { USER_OWNER };
- }
- }
-
- private void removeUsersForTest(int[] users) throws DeviceNotAvailableException {
- for (int user : users) {
- if (user != USER_OWNER) {
- removeUserOnDevice(getDevice(), user);
- }
- }
- }
-
- private static int createUserOnDevice(ITestDevice device) throws DeviceNotAvailableException {
- // TODO: move this to ITestDevice once it supports users
- final String name = "CTS_" + System.currentTimeMillis();
- final String output = device.executeShellCommand("pm create-user " + name);
- if (output.startsWith("Success")) {
- try {
- final int userId = Integer.parseInt(
- output.substring(output.lastIndexOf(" ")).trim());
- device.executeShellCommand("am start-user " + userId);
- return userId;
- } catch (NumberFormatException e) {
- fail("Failed to parse result: " + output);
- }
- } else {
- fail("Failed to create user: " + output);
- }
- throw new IllegalStateException();
- }
-
- private static void removeUserOnDevice(ITestDevice device, int userId)
- throws DeviceNotAvailableException {
- // TODO: move this to ITestDevice once it supports users
- final String output = device.executeShellCommand("pm remove-user " + userId);
- if (output.startsWith("Error")) {
- fail("Failed to remove user: " + output);
- }
- }
-
- private TestRunResult doRunTests(String pkgName, String testClassName, String testMethodName,
- int userId) throws DeviceNotAvailableException {
- // TODO: move this to RemoteAndroidTestRunner once it supports users
- final StringBuilder cmd = new StringBuilder("am instrument --user " + userId + " -w -r");
- if (testClassName != null) {
- cmd.append(" -e class " + testClassName);
- if (testMethodName != null) {
- cmd.append("#" + testMethodName);
- }
- }
- cmd.append(" " + pkgName + "/" + RUNNER);
-
- Log.i(LOG_TAG, "Running " + cmd + " on " + getDevice().getSerialNumber());
-
- CollectingTestListener listener = new CollectingTestListener();
- InstrumentationResultParser parser = new InstrumentationResultParser(pkgName, listener);
-
- getDevice().executeShellCommand(cmd.toString(), parser);
- return listener.getCurrentRunResults();
- }
-
- private static void wipePrimaryExternalStorage(ITestDevice device)
- throws DeviceNotAvailableException {
- device.executeShellCommand("rm -rf /sdcard/*");
+ Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
}
}
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/DocumentsTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/DocumentsTest.java
index fbde558..a4ec65a 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/DocumentsTest.java
@@ -24,6 +24,10 @@
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.testtype.IBuildReceiver;
+/**
+ * Set of tests that verify behavior of
+ * {@link android.provider.DocumentsContract} and related intents.
+ */
public class DocumentsTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
private static final String PROVIDER_PKG = "com.android.cts.documentprovider";
private static final String PROVIDER_APK = "CtsDocumentProvider.apk";
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java
new file mode 100644
index 0000000..d74ec52
--- /dev/null
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java
@@ -0,0 +1,245 @@
+/*
+ * 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.cts.util.AbiUtils;
+import com.android.ddmlib.Log;
+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;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Set of tests that verify behavior of external storage devices.
+ */
+public class ExternalStorageHostTest extends DeviceTestCase
+ implements IAbiReceiver, IBuildReceiver {
+ private static final String TAG = "ExternalStorageHostTest";
+
+ private static final String COMMON_CLASS =
+ "com.android.cts.externalstorageapp.CommonExternalStorageTest";
+
+ private static final String NONE_APK = "CtsExternalStorageApp.apk";
+ private static final String NONE_PKG = "com.android.cts.externalstorageapp";
+ private static final String NONE_CLASS = ".ExternalStorageTest";
+ private static final String READ_APK = "CtsReadExternalStorageApp.apk";
+ private static final String READ_PKG = "com.android.cts.readexternalstorageapp";
+ private static final String READ_CLASS = ".ReadExternalStorageTest";
+ private static final String WRITE_APK = "CtsWriteExternalStorageApp.apk";
+ private static final String WRITE_PKG = "com.android.cts.writeexternalstorageapp";
+ private static final String WRITE_CLASS = ".WriteExternalStorageTest";
+ private static final String MULTIUSER_APK = "CtsMultiUserStorageApp.apk";
+ private static final String MULTIUSER_PKG = "com.android.cts.multiuserstorageapp";
+ private static final String MULTIUSER_CLASS = ".MultiUserStorageTest";
+
+ private IAbi mAbi;
+ private CtsBuildHelper mCtsBuild;
+
+ @Override
+ public void setAbi(IAbi abi) {
+ mAbi = abi;
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+ }
+
+ private File getTestAppFile(String fileName) throws FileNotFoundException {
+ return mCtsBuild.getTestApp(fileName);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ assertNotNull(mAbi);
+ assertNotNull(mCtsBuild);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Verify that app with no external storage permissions works correctly.
+ */
+ public void testExternalStorageNone() throws Exception {
+ final int[] users = createUsersForTest();
+ try {
+ wipePrimaryExternalStorage();
+
+ getDevice().uninstallPackage(NONE_PKG);
+ String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
+
+ for (int user : users) {
+ runDeviceTests(NONE_PKG, COMMON_CLASS, user);
+ runDeviceTests(NONE_PKG, NONE_CLASS, user);
+ }
+ } finally {
+ getDevice().uninstallPackage(NONE_PKG);
+ removeUsersForTest(users);
+ }
+ }
+
+ /**
+ * Verify that app with
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} works
+ * correctly.
+ */
+ public void testExternalStorageRead() throws Exception {
+ final int[] users = createUsersForTest();
+ try {
+ wipePrimaryExternalStorage();
+
+ getDevice().uninstallPackage(READ_PKG);
+ String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
+
+ for (int user : users) {
+ runDeviceTests(READ_PKG, COMMON_CLASS, user);
+ runDeviceTests(READ_PKG, READ_CLASS, user);
+ }
+ } finally {
+ getDevice().uninstallPackage(READ_PKG);
+ removeUsersForTest(users);
+ }
+ }
+
+ /**
+ * Verify that app with
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} works
+ * correctly.
+ */
+ public void testExternalStorageWrite() throws Exception {
+ final int[] users = createUsersForTest();
+ try {
+ wipePrimaryExternalStorage();
+
+ getDevice().uninstallPackage(WRITE_PKG);
+ String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
+
+ for (int user : users) {
+ runDeviceTests(WRITE_PKG, COMMON_CLASS, user);
+ runDeviceTests(WRITE_PKG, WRITE_CLASS, user);
+ }
+ } finally {
+ getDevice().uninstallPackage(WRITE_PKG);
+ removeUsersForTest(users);
+ }
+ }
+
+ /**
+ * Verify that app with WRITE_EXTERNAL can leave gifts in external storage
+ * directories belonging to other apps, and those apps can read.
+ */
+ public void testExternalStorageGifts() throws Exception {
+ final int[] users = createUsersForTest();
+ try {
+ wipePrimaryExternalStorage();
+
+ getDevice().uninstallPackage(NONE_PKG);
+ getDevice().uninstallPackage(READ_PKG);
+ getDevice().uninstallPackage(WRITE_PKG);
+ String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
+ assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
+ assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
+
+ for (int user : users) {
+ runDeviceTests(WRITE_PKG, "WriteGiftTest", user);
+ runDeviceTests(READ_PKG, "ReadGiftTest", user);
+ runDeviceTests(NONE_PKG, "GiftTest", user);
+ }
+ } finally {
+ getDevice().uninstallPackage(NONE_PKG);
+ getDevice().uninstallPackage(READ_PKG);
+ getDevice().uninstallPackage(WRITE_PKG);
+ removeUsersForTest(users);
+ }
+ }
+
+ /**
+ * Test multi-user emulated storage environment, ensuring that each user has
+ * isolated storage.
+ */
+ public void testMultiUserStorageIsolated() throws Exception {
+ final int[] users = createUsersForTest();
+ try {
+ if (users.length == 1) {
+ Log.d(TAG, "Single user device; skipping isolated storage tests");
+ return;
+ }
+
+ final int owner = users[0];
+ final int secondary = users[1];
+
+ // Install our test app
+ getDevice().uninstallPackage(MULTIUSER_PKG);
+ String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ final String installResult = getDevice()
+ .installPackage(getTestAppFile(MULTIUSER_APK), false, options);
+ assertNull("Failed to install: " + installResult, installResult);
+
+ // Clear data from previous tests
+ runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testCleanIsolatedStorage", owner);
+ runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testCleanIsolatedStorage", secondary);
+
+ // Have both users try writing into isolated storage
+ runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testWriteIsolatedStorage", owner);
+ runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testWriteIsolatedStorage", secondary);
+
+ // Verify they both have isolated view of storage
+ runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testReadIsolatedStorage", owner);
+ runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testReadIsolatedStorage", secondary);
+ } finally {
+ getDevice().uninstallPackage(MULTIUSER_PKG);
+ removeUsersForTest(users);
+ }
+ }
+
+ private void wipePrimaryExternalStorage() throws DeviceNotAvailableException {
+ getDevice().executeShellCommand("rm -rf /sdcard/*");
+ }
+
+ private int[] createUsersForTest() throws DeviceNotAvailableException {
+ return Utils.createUsersForTest(getDevice());
+ }
+
+ private void removeUsersForTest(int[] users) throws DeviceNotAvailableException {
+ Utils.removeUsersForTest(getDevice(), users);
+ }
+
+ private void runDeviceTests(String packageName, String testClassName, int userId)
+ throws DeviceNotAvailableException {
+ Utils.runDeviceTests(getDevice(), packageName, testClassName, userId);
+ }
+
+ private void runDeviceTests(String packageName, String testClassName, String testMethodName,
+ int userId) throws DeviceNotAvailableException {
+ Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId);
+ }
+}
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java
index 091da24..4b5259d 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java
@@ -24,6 +24,10 @@
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.testtype.IBuildReceiver;
+/**
+ * Set of tests that verify behavior of runtime permissions, including both
+ * dynamic granting and behavior of legacy apps.
+ */
public class PermissionsHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
private static final String PKG = "com.android.cts.usepermission";
@@ -87,6 +91,7 @@
public void testGranted() throws Exception {
assertNull(getDevice().installPackage(mCtsBuild.getTestApp(APK), false, false));
+ grantPermission(PKG, "android.permission.READ_EXTERNAL_STORAGE");
grantPermission(PKG, "android.permission.WRITE_EXTERNAL_STORAGE");
runDeviceTests(PKG, ".UsePermissionTest", "testGranted");
}
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
index 9de7d84..ef3af8d 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
@@ -36,14 +36,15 @@
* Tests that verify installing of various split APKs from host side.
*/
public class SplitTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
- private static final String PKG = "com.android.cts.splitapp";
+ static final String PKG = "com.android.cts.splitapp";
+ static final String CLASS = ".SplitAppTest";
- private static final String APK = "CtsSplitApp.apk";
+ static final String APK = "CtsSplitApp.apk";
- private static final String APK_mdpi = "CtsSplitApp_mdpi-v4.apk";
- private static final String APK_hdpi = "CtsSplitApp_hdpi-v4.apk";
- private static final String APK_xhdpi = "CtsSplitApp_xhdpi-v4.apk";
- private static final String APK_xxhdpi = "CtsSplitApp_xxhdpi-v4.apk";
+ static final String APK_mdpi = "CtsSplitApp_mdpi-v4.apk";
+ static final String APK_hdpi = "CtsSplitApp_hdpi-v4.apk";
+ static final String APK_xhdpi = "CtsSplitApp_xhdpi-v4.apk";
+ static final String APK_xxhdpi = "CtsSplitApp_xxhdpi-v4.apk";
private static final String APK_v7 = "CtsSplitApp_v7.apk";
private static final String APK_fr = "CtsSplitApp_fr.apk";
@@ -69,7 +70,7 @@
private static final String APK_FEATURE = "CtsSplitAppFeature.apk";
private static final String APK_FEATURE_v7 = "CtsSplitAppFeature_v7.apk";
- private static final HashMap<String, String> ABI_TO_APK = new HashMap<>();
+ static final HashMap<String, String> ABI_TO_APK = new HashMap<>();
static {
ABI_TO_APK.put("x86", APK_x86);
@@ -113,18 +114,18 @@
public void testSingleBase() throws Exception {
new InstallMultiple().addApk(APK).run();
- runDeviceTests(PKG, ".SplitAppTest", "testSingleBase");
+ runDeviceTests(PKG, CLASS, "testSingleBase");
}
public void testDensitySingle() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_mdpi).run();
- runDeviceTests(PKG, ".SplitAppTest", "testDensitySingle");
+ runDeviceTests(PKG, CLASS, "testDensitySingle");
}
public void testDensityAll() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_mdpi).addApk(APK_hdpi).addApk(APK_xhdpi)
.addApk(APK_xxhdpi).run();
- runDeviceTests(PKG, ".SplitAppTest", "testDensityAll");
+ runDeviceTests(PKG, CLASS, "testDensityAll");
}
/**
@@ -133,11 +134,11 @@
*/
public void testDensityBest() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_mdpi).run();
- runDeviceTests(PKG, ".SplitAppTest", "testDensityBest1");
+ runDeviceTests(PKG, CLASS, "testDensityBest1");
// Now splice in an additional split which offers better resources
new InstallMultiple().inheritFrom(PKG).addApk(APK_xxhdpi).run();
- runDeviceTests(PKG, ".SplitAppTest", "testDensityBest2");
+ runDeviceTests(PKG, CLASS, "testDensityBest2");
}
/**
@@ -146,12 +147,12 @@
*/
public void testApi() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_v7).run();
- runDeviceTests(PKG, ".SplitAppTest", "testApi");
+ runDeviceTests(PKG, CLASS, "testApi");
}
public void testLocale() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_de).addApk(APK_fr).run();
- runDeviceTests(PKG, ".SplitAppTest", "testLocale");
+ runDeviceTests(PKG, CLASS, "testLocale");
}
/**
@@ -164,7 +165,7 @@
assertNotNull("Failed to find APK for ABI " + abi, apk);
new InstallMultiple().addApk(APK).addApk(apk).run();
- runDeviceTests(PKG, ".SplitAppTest", "testNative");
+ runDeviceTests(PKG, CLASS, "testNative");
}
/**
@@ -179,7 +180,7 @@
assertNotNull("Failed to find APK for ABI " + abi, apk);
new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
- runDeviceTests(PKG, ".SplitAppTest", "testNative");
+ runDeviceTests(PKG, CLASS, "testNative");
}
/**
@@ -192,7 +193,7 @@
inst.addApk(apk);
}
inst.run();
- runDeviceTests(PKG, ".SplitAppTest", "testNative");
+ runDeviceTests(PKG, CLASS, "testNative");
}
/**
@@ -207,7 +208,7 @@
inst.addApk(apk);
}
inst.run();
- runDeviceTests(PKG, ".SplitAppTest", "testNative");
+ runDeviceTests(PKG, CLASS, "testNative");
}
public void testDuplicateBase() throws Exception {
@@ -238,21 +239,21 @@
public void testDiffRevision() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
- runDeviceTests(PKG, ".SplitAppTest", "testRevision0_12");
+ runDeviceTests(PKG, CLASS, "testRevision0_12");
}
public void testDiffRevisionInheritBase() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_v7).run();
- runDeviceTests(PKG, ".SplitAppTest", "testRevision0_0");
+ runDeviceTests(PKG, CLASS, "testRevision0_0");
new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION_v7).run();
- runDeviceTests(PKG, ".SplitAppTest", "testRevision0_12");
+ runDeviceTests(PKG, CLASS, "testRevision0_12");
}
public void testDiffRevisionInheritSplit() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_v7).run();
- runDeviceTests(PKG, ".SplitAppTest", "testRevision0_0");
+ runDeviceTests(PKG, CLASS, "testRevision0_0");
new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION).run();
- runDeviceTests(PKG, ".SplitAppTest", "testRevision12_0");
+ runDeviceTests(PKG, CLASS, "testRevision12_0");
}
public void testDiffRevisionDowngrade() throws Exception {
@@ -262,12 +263,12 @@
public void testFeatureBase() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_FEATURE).run();
- runDeviceTests(PKG, ".SplitAppTest", "testFeatureBase");
+ runDeviceTests(PKG, CLASS, "testFeatureBase");
}
public void testFeatureApi() throws Exception {
new InstallMultiple().addApk(APK).addApk(APK_FEATURE).addApk(APK_FEATURE_v7).run();
- runDeviceTests(PKG, ".SplitAppTest", "testFeatureApi");
+ runDeviceTests(PKG, CLASS, "testFeatureApi");
}
public void testInheritUpdatedBase() throws Exception {
@@ -283,39 +284,72 @@
*/
public void testClearCodeCache() throws Exception {
new InstallMultiple().addApk(APK).run();
- runDeviceTests(PKG, ".SplitAppTest", "testCodeCacheWrite");
+ runDeviceTests(PKG, CLASS, "testCodeCacheWrite");
new InstallMultiple().addArg("-r").addApk(APK_DIFF_VERSION).run();
- runDeviceTests(PKG, ".SplitAppTest", "testCodeCacheRead");
+ runDeviceTests(PKG, CLASS, "testCodeCacheRead");
}
- class InstallMultiple {
- private List<String> mArgs = new ArrayList<>();
- private List<File> mApks = new ArrayList<>();
+ private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
+ public InstallMultiple() {
+ super(getDevice(), mCtsBuild, mAbi);
+ }
+ }
+
+ public static class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
+ private final ITestDevice mDevice;
+ private final CtsBuildHelper mBuild;
+ private final IAbi mAbi;
+
+ private final List<String> mArgs = new ArrayList<>();
+ private final List<File> mApks = new ArrayList<>();
private boolean mUseNaturalAbi;
- public InstallMultiple() {
+ public BaseInstallMultiple(ITestDevice device, CtsBuildHelper build, IAbi abi) {
+ mDevice = device;
+ mBuild = build;
+ mAbi = abi;
addArg("-g");
}
- InstallMultiple addArg(String arg) {
+ T addArg(String arg) {
mArgs.add(arg);
- return this;
+ return (T) this;
}
- InstallMultiple addApk(String apk) throws FileNotFoundException {
- mApks.add(mCtsBuild.getTestApp(apk));
- return this;
+ T addApk(String apk) throws FileNotFoundException {
+ mApks.add(mBuild.getTestApp(apk));
+ return (T) this;
}
- InstallMultiple inheritFrom(String packageName) {
+ T inheritFrom(String packageName) {
addArg("-r");
addArg("-p " + packageName);
- return this;
+ return (T) this;
}
- InstallMultiple useNaturalAbi() {
+ T useNaturalAbi() {
mUseNaturalAbi = true;
- return this;
+ return (T) this;
+ }
+
+ T locationAuto() {
+ addArg("--install-location 0");
+ return (T) this;
+ }
+
+ T locationInternalOnly() {
+ addArg("--install-location 1");
+ return (T) this;
+ }
+
+ T locationPreferExternal() {
+ addArg("--install-location 2");
+ return (T) this;
+ }
+
+ T forceUuid(String uuid) {
+ addArg("--force-uuid " + uuid);
+ return (T) this;
}
void run() throws DeviceNotAvailableException {
@@ -327,7 +361,7 @@
}
private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
- final ITestDevice device = getDevice();
+ final ITestDevice device = mDevice;
// Create an install session
final StringBuilder cmd = new StringBuilder();
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/Utils.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/Utils.java
index c58d6bf..fdf84d3 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/Utils.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/Utils.java
@@ -16,6 +16,7 @@
package com.android.cts.appsecurity;
+import com.android.ddmlib.Log;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.ddmlib.testrunner.TestResult;
@@ -28,13 +29,37 @@
import java.util.Map;
public class Utils {
+ private static final String TAG = "AppSecurity";
+
+ public static final int USER_OWNER = 0;
+
public static void runDeviceTests(ITestDevice device, String packageName)
throws DeviceNotAvailableException {
- runDeviceTests(device, packageName, null, null);
+ runDeviceTests(device, packageName, null, null, USER_OWNER);
+ }
+
+ public static void runDeviceTests(ITestDevice device, String packageName, int userId)
+ throws DeviceNotAvailableException {
+ runDeviceTests(device, packageName, null, null, userId);
+ }
+
+ public static void runDeviceTests(ITestDevice device, String packageName, String testClassName)
+ throws DeviceNotAvailableException {
+ runDeviceTests(device, packageName, testClassName, null, USER_OWNER);
+ }
+
+ public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
+ int userId) throws DeviceNotAvailableException {
+ runDeviceTests(device, packageName, testClassName, null, userId);
}
public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
String testMethodName) throws DeviceNotAvailableException {
+ runDeviceTests(device, packageName, testClassName, testMethodName, USER_OWNER);
+ }
+
+ public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
+ String testMethodName, int userId) throws DeviceNotAvailableException {
if (testClassName != null && testClassName.startsWith(".")) {
testClassName = packageName + testClassName;
}
@@ -43,6 +68,13 @@
"android.support.test.runner.AndroidJUnitRunner", device.getIDevice());
if (testClassName != null && testMethodName != null) {
testRunner.setMethodName(testClassName, testMethodName);
+ } else if (testClassName != null) {
+ testRunner.setClassName(testClassName);
+ }
+
+ if (userId != USER_OWNER) {
+ // TODO: move this to RemoteAndroidTestRunner once it supports users
+ testRunner.addInstrumentationArg("hack_key", "hack_value --user " + userId);
}
final CollectingTestListener listener = new CollectingTestListener();
@@ -68,4 +100,66 @@
throw new AssertionError(errorBuilder.toString());
}
}
+
+ private static boolean isMultiUserSupportedOnDevice(ITestDevice device)
+ throws DeviceNotAvailableException {
+ // TODO: move this to ITestDevice once it supports users
+ final String output = device.executeShellCommand("pm get-max-users");
+ try {
+ return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim()) > 1;
+ } catch (NumberFormatException e) {
+ throw new AssertionError("Failed to parse result: " + output);
+ }
+ }
+
+ /**
+ * Return set of users that test should be run for, creating a secondary
+ * user if the device supports it. Always call
+ * {@link #removeUsersForTest(ITestDevice, int[])} when finished.
+ */
+ public static int[] createUsersForTest(ITestDevice device) throws DeviceNotAvailableException {
+ if (isMultiUserSupportedOnDevice(device)) {
+ return new int[] { USER_OWNER, createUserOnDevice(device) };
+ } else {
+ Log.d(TAG, "Single user device; skipping isolated storage tests");
+ return new int[] { USER_OWNER };
+ }
+ }
+
+ public static void removeUsersForTest(ITestDevice device, int[] users)
+ throws DeviceNotAvailableException {
+ for (int user : users) {
+ if (user != USER_OWNER) {
+ removeUserOnDevice(device, user);
+ }
+ }
+ }
+
+ private static int createUserOnDevice(ITestDevice device) throws DeviceNotAvailableException {
+ // TODO: move this to ITestDevice once it supports users
+ final String name = "CTS_" + System.currentTimeMillis();
+ final String output = device.executeShellCommand("pm create-user " + name);
+ if (output.startsWith("Success")) {
+ try {
+ final int userId = Integer.parseInt(
+ output.substring(output.lastIndexOf(" ")).trim());
+ device.executeShellCommand("am start-user " + userId);
+ return userId;
+ } catch (NumberFormatException e) {
+ throw new AssertionError("Failed to parse result: " + output);
+ }
+ } else {
+ throw new AssertionError("Failed to create user: " + output);
+ }
+ }
+
+ private static void removeUserOnDevice(ITestDevice device, int userId)
+ throws DeviceNotAvailableException {
+ // TODO: move this to ITestDevice once it supports users
+ final String output = device.executeShellCommand("pm remove-user " + userId);
+ if (output.startsWith("Error")) {
+ throw new AssertionError("Failed to remove user: " + output);
+ }
+ }
+
}
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 379de5f..c935c91 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
@@ -100,7 +100,7 @@
for (File path : paths) {
assertNotNull("Valid media must be inserted during CTS", path);
assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
- Environment.getStorageState(path));
+ Environment.getExternalStorageState(path));
assertDirReadWriteAccess(path);
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
index 5d492b2..7dc462a 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
@@ -16,16 +16,9 @@
package com.android.cts.externalstorageapp;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
import android.app.DownloadManager;
import android.app.DownloadManager.Query;
@@ -75,7 +68,7 @@
}
// Keep walking up until we leave device
- while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+ while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
assertDirNoAccess(path);
path = path.getParentFile();
}
@@ -120,21 +113,6 @@
}
/**
- * Verify we can read only our gifts.
- */
- public void doVerifyGifts() throws Exception {
- final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
- assertFileReadWriteAccess(none);
- assertEquals(100, readInt(none));
-
- final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
- assertFileNoAccess(read);
-
- final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
- assertFileNoAccess(write);
- }
-
- /**
* Shamelessly borrowed from DownloadManagerTest.java
*/
private static class DownloadCompleteReceiver extends BroadcastReceiver {
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
new file mode 100644
index 0000000..e482b2f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.externalstorageapp;
+
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+
+import android.test.AndroidTestCase;
+
+import java.io.File;
+
+public class GiftTest extends AndroidTestCase {
+ /**
+ * Verify we can read only our gifts.
+ */
+ public void testGifts() throws Exception {
+ final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
+ assertFileReadWriteAccess(none);
+ assertEquals(100, readInt(none));
+
+ final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
+ assertFileNoAccess(read);
+
+ final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
+ assertFileNoAccess(write);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/src/com/android/cts/multiuserstorageapp/MultiUserStorageTest.java b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/src/com/android/cts/multiuserstorageapp/MultiUserStorageTest.java
index 2a80c75..ed84a66 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/src/com/android/cts/multiuserstorageapp/MultiUserStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/src/com/android/cts/multiuserstorageapp/MultiUserStorageTest.java
@@ -45,7 +45,9 @@
private void wipeTestFiles(File dir) {
dir.mkdirs();
- for (File file : dir.listFiles()) {
+ final File[] files = dir.listFiles();
+ if (files == null) return;
+ for (File file : files) {
if (file.getName().startsWith(FILE_PREFIX)) {
Log.d(TAG, "Wiping " + file);
file.delete();
@@ -53,11 +55,11 @@
}
}
- public void cleanIsolatedStorage() throws Exception {
+ public void testCleanIsolatedStorage() throws Exception {
wipeTestFiles(Environment.getExternalStorageDirectory());
}
- public void writeIsolatedStorage() throws Exception {
+ public void testWriteIsolatedStorage() throws Exception {
final int uid = android.os.Process.myUid();
writeInt(buildApiPath(FILE_SINGLETON), uid);
@@ -67,13 +69,13 @@
for (File path : getAllPackageSpecificPathsExceptObb(getContext())) {
assertNotNull("Valid media must be inserted during CTS", path);
assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
- Environment.getStorageState(path));
+ Environment.getExternalStorageState(path));
writeInt(new File(path, FILE_SINGLETON), uid);
}
}
- public void readIsolatedStorage() throws Exception {
+ public void testReadIsolatedStorage() throws Exception {
final int uid = android.os.Process.myUid();
// Expect that the value we wrote earlier is still valid and wasn't
@@ -91,23 +93,23 @@
for (File path : getAllPackageSpecificPathsExceptObb(getContext())) {
assertNotNull("Valid media must be inserted during CTS", path);
assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
- Environment.getStorageState(path));
+ Environment.getExternalStorageState(path));
assertEquals("Unexpected value in singleton file at " + path, uid,
readInt(new File(path, FILE_SINGLETON)));
}
}
- public void cleanObbStorage() throws Exception {
+ public void testCleanObbStorage() throws Exception {
wipeTestFiles(getContext().getObbDir());
}
- public void writeObbStorage() throws Exception {
+ public void testWriteObbStorage() throws Exception {
writeInt(buildApiObbPath(FILE_OBB_API_SINGLETON), OBB_API_VALUE);
writeInt(buildEnvObbPath(FILE_OBB_SINGLETON), OBB_VALUE);
}
- public void readObbStorage() throws Exception {
+ public void testReadObbStorage() throws Exception {
assertEquals("Failed to read OBB file from API path", OBB_API_VALUE,
readInt(buildApiObbPath(FILE_OBB_API_SINGLETON)));
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
index bbd1e7a..71faab2 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
@@ -16,16 +16,9 @@
package com.android.cts.readexternalstorageapp;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadOnlyAccess;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadOnlyAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
import android.os.Environment;
import android.test.AndroidTestCase;
@@ -54,7 +47,7 @@
for (File path : paths) {
assertNotNull("Valid media must be inserted during CTS", path);
assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
- Environment.getStorageState(path));
+ Environment.getExternalStorageState(path));
assertTrue(path.getAbsolutePath().contains(packageName));
@@ -65,27 +58,10 @@
}
// Keep walking up until we leave device
- while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+ while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
assertDirReadOnlyAccess(path);
path = path.getParentFile();
}
}
}
-
- /**
- * Verify we can read all gifts.
- */
- public void doVerifyGifts() throws Exception {
- final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
- assertFileReadOnlyAccess(none);
- assertEquals(100, readInt(none));
-
- final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
- assertFileReadWriteAccess(read);
- assertEquals(101, readInt(read));
-
- final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
- assertFileReadOnlyAccess(write);
- assertEquals(102, readInt(write));
- }
}
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.java b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.java
new file mode 100644
index 0000000..e72be77
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.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 com.android.cts.readexternalstorageapp;
+
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadOnlyAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+
+import android.test.AndroidTestCase;
+
+import java.io.File;
+
+public class ReadGiftTest extends AndroidTestCase {
+ /**
+ * Verify we can read all gifts.
+ */
+ public void testGifts() throws Exception {
+ final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
+ assertFileReadOnlyAccess(none);
+ assertEquals(100, readInt(none));
+
+ final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
+ assertFileReadWriteAccess(read);
+ assertEquals(101, readInt(read));
+
+ final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
+ assertFileReadOnlyAccess(write);
+ assertEquals(102, readInt(write));
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index 3d6cee7..c5f0fd0 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -19,6 +19,7 @@
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -27,9 +28,17 @@
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.StatFs;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.util.DisplayMetrics;
@@ -39,7 +48,12 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
@@ -51,9 +65,14 @@
private static final String TAG = "SplitAppTest";
private static final String PKG = "com.android.cts.splitapp";
+ private static final long MB_IN_BYTES = 1 * 1024 * 1024;
+
public static boolean sFeatureTouched = false;
public static String sFeatureValue = null;
+ public void testNothing() throws Exception {
+ }
+
public void testSingleBase() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
@@ -311,6 +330,131 @@
assertEquals(0, result.size());
}
+ /**
+ * Write app data in a number of locations that expect to remain intact over
+ * long periods of time, such as across app moves.
+ */
+ public void testDataWrite() throws Exception {
+ final String token = String.valueOf(android.os.Process.myUid());
+ writeString(getContext().getFileStreamPath("my_int"), token);
+
+ final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db",
+ Context.MODE_PRIVATE, null);
+ try {
+ db.execSQL("DROP TABLE IF EXISTS my_table");
+ db.execSQL("CREATE TABLE my_table(value INTEGER)");
+ db.execSQL("INSERT INTO my_table VALUES (101), (102), (103)");
+ } finally {
+ db.close();
+ }
+ }
+
+ /**
+ * Verify that data written by {@link #testDataWrite()} is still intact.
+ */
+ public void testDataRead() throws Exception {
+ final String token = String.valueOf(android.os.Process.myUid());
+ assertEquals(token, readString(getContext().getFileStreamPath("my_int")));
+
+ final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db",
+ Context.MODE_PRIVATE, null);
+ try {
+ final Cursor cursor = db.query("my_table", null, null, null, null, null, "value ASC");
+ try {
+ assertEquals(3, cursor.getCount());
+ assertTrue(cursor.moveToPosition(0));
+ assertEquals(101, cursor.getInt(0));
+ assertTrue(cursor.moveToPosition(1));
+ assertEquals(102, cursor.getInt(0));
+ assertTrue(cursor.moveToPosition(2));
+ assertEquals(103, cursor.getInt(0));
+ } finally {
+ cursor.close();
+ }
+ } finally {
+ db.close();
+ }
+ }
+
+ /**
+ * Verify that app is installed on internal storage.
+ */
+ public void testDataInternal() throws Exception {
+ final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
+ final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
+ assertEquals(internal.st_dev, actual.st_dev);
+ }
+
+ /**
+ * Verify that app is not installed on internal storage.
+ */
+ public void testDataNotInternal() throws Exception {
+ final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
+ final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
+ MoreAsserts.assertNotEqual(internal.st_dev, actual.st_dev);
+ }
+
+ public void testPrimaryDataWrite() throws Exception {
+ final String token = String.valueOf(android.os.Process.myUid());
+ writeString(new File(getContext().getExternalFilesDir(null), "my_ext"), token);
+ }
+
+ public void testPrimaryDataRead() throws Exception {
+ final String token = String.valueOf(android.os.Process.myUid());
+ assertEquals(token, readString(new File(getContext().getExternalFilesDir(null), "my_ext")));
+ }
+
+ /**
+ * Verify shared storage behavior when on internal storage.
+ */
+ public void testPrimaryInternal() throws Exception {
+ assertTrue("emulated", Environment.isExternalStorageEmulated());
+ assertFalse("removable", Environment.isExternalStorageRemovable());
+ assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+ }
+
+ /**
+ * Verify shared storage behavior when on physical storage.
+ */
+ public void testPrimaryPhysical() throws Exception {
+ assertFalse("emulated", Environment.isExternalStorageEmulated());
+ assertTrue("removable", Environment.isExternalStorageRemovable());
+ assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+ }
+
+ /**
+ * Verify shared storage behavior when on adopted storage.
+ */
+ public void testPrimaryAdopted() throws Exception {
+ assertTrue("emulated", Environment.isExternalStorageEmulated());
+ assertTrue("removable", Environment.isExternalStorageRemovable());
+ assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+ }
+
+ /**
+ * Verify that shared storage is unmounted.
+ */
+ public void testPrimaryUnmounted() throws Exception {
+ MoreAsserts.assertNotEqual(Environment.MEDIA_MOUNTED,
+ Environment.getExternalStorageState());
+ }
+
+ /**
+ * Verify that shared storage lives on same volume as app.
+ */
+ public void testPrimaryOnSameVolume() throws Exception {
+ final File current = getContext().getFilesDir();
+ final File primary = Environment.getExternalStorageDirectory();
+
+ // Shared storage may jump through another filesystem for permission
+ // enforcement, so we verify that total/free space are identical.
+ final long totalDelta = Math.abs(current.getTotalSpace() - primary.getTotalSpace());
+ final long freeDelta = Math.abs(current.getFreeSpace() - primary.getFreeSpace());
+ if (totalDelta > MB_IN_BYTES || freeDelta > MB_IN_BYTES) {
+ fail("Expected primary storage to be on same volume as app");
+ }
+ }
+
public void testCodeCacheWrite() throws Exception {
assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile());
assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile());
@@ -390,4 +534,22 @@
if (in != null) in.close();
}
}
+
+ private static void writeString(File file, String value) throws IOException {
+ final DataOutputStream os = new DataOutputStream(new FileOutputStream(file));
+ try {
+ os.writeUTF(value);
+ } finally {
+ os.close();
+ }
+ }
+
+ private static String readString(File file) throws IOException {
+ final DataInputStream is = new DataInputStream(new FileInputStream(file));
+ try {
+ return is.readUTF();
+ } finally {
+ is.close();
+ }
+ }
}
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
index d464e48..5137ee8 100644
--- 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
@@ -46,6 +46,8 @@
// New permission model is denied by default
assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
+ .checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE));
+ assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE));
assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
assertDirNoAccess(Environment.getExternalStorageDirectory());
@@ -56,6 +58,8 @@
logCommand("/system/bin/cat", "/proc/self/mountinfo");
assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+ .checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE));
+ assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE));
assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
@@ -67,6 +71,8 @@
// Start out without permission
assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
+ .checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE));
+ assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE));
assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
assertDirNoAccess(Environment.getExternalStorageDirectory());
@@ -79,6 +85,7 @@
mDevice.waitForIdle();
mActivity.requestPermissions(new String[] {
+ android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE }, 42);
mDevice.waitForIdle();
@@ -86,15 +93,19 @@
.resourceId("com.android.packageinstaller:id/permission_allow_button")).click();
mDevice.waitForIdle();
- final MyActivity.Result result = mActivity.getResult();
+ MyActivity.Result result = mActivity.getResult();
assertEquals(42, result.requestCode);
- assertEquals(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, result.permissions[0]);
+ assertEquals(android.Manifest.permission.READ_EXTERNAL_STORAGE, result.permissions[0]);
+ assertEquals(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, result.permissions[1]);
assertEquals(PackageManager.PERMISSION_GRANTED, result.grantResults[0]);
+ assertEquals(PackageManager.PERMISSION_GRANTED, result.grantResults[1]);
logCommand("/system/bin/cat", "/proc/self/mountinfo");
// We should have permission now!
assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+ .checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE));
+ assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE));
assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
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
index b30e8a5..fb87e81 100644
--- 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
@@ -18,42 +18,62 @@
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.logCommand;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.os.Process;
import android.test.InstrumentationTestCase;
-import com.android.cts.externalstorageapp.CommonExternalStorageTest;
+import java.io.File;
public class UsePermissionCompatTest extends InstrumentationTestCase {
private static final String TAG = "UsePermissionTest";
public void testCompatDefault() throws Exception {
+ final Context context = getInstrumentation().getContext();
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()));
+ context.checkPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE,
+ Process.myPid(), Process.myUid()));
+ assertEquals(PackageManager.PERMISSION_GRANTED,
+ context.checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Process.myPid(), Process.myUid()));
assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
- assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+ for (File path : getAllPackageSpecificPaths(context)) {
+ if (path != null) {
+ assertDirReadWriteAccess(path);
+ }
+ }
}
public void testCompatRevoked() throws Exception {
- CommonExternalStorageTest.logCommand("/system/bin/cat", "/proc/self/mountinfo");
+ final Context context = getInstrumentation().getContext();
+ 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()));
+ context.checkPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE,
+ Process.myPid(), Process.myUid()));
+ assertEquals(PackageManager.PERMISSION_GRANTED,
+ context.checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Process.myPid(), Process.myUid()));
assertEquals(Environment.MEDIA_UNMOUNTED, Environment.getExternalStorageState());
assertDirNoAccess(Environment.getExternalStorageDirectory());
- assertNull(getInstrumentation().getContext().getExternalCacheDir());
+ for (File dir : getAllPackageSpecificPaths(context)) {
+ if (dir != null) {
+ assertDirNoAccess(dir);
+ }
+ }
+
+ // Just to be sure, poke explicit path
+ assertDirNoAccess(new File(Environment.getExternalStorageDirectory(),
+ "/Android/data/" + getInstrumentation().getContext().getPackageName()));
}
}
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
index afee854..badc852 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
@@ -17,14 +17,10 @@
package com.android.cts.writeexternalstorageapp;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.TAG;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoWriteAccess;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadOnlyAccess;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildProbeFile;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.deleteContents;
import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
@@ -142,12 +138,12 @@
for (File path : paths) {
assertNotNull("Valid media must be inserted during CTS", path);
assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
- Environment.getStorageState(path));
+ Environment.getExternalStorageState(path));
assertTrue(path.getAbsolutePath().contains(packageName));
// Walk until we leave device, writing the whole way
- while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+ while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
assertDirReadWriteAccess(path);
path = path.getParentFile();
}
@@ -179,7 +175,7 @@
int depth = 0;
while (depth++ < 32) {
assertDirReadWriteAccess(path);
- assertEquals(Environment.MEDIA_MOUNTED, Environment.getStorageState(path));
+ assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState(path));
if (path.getAbsolutePath().equals(top.getAbsolutePath())) {
break;
@@ -194,7 +190,7 @@
// And going one step further should be outside our reach
path = path.getParentFile();
assertDirNoWriteAccess(path);
- assertEquals(Environment.MEDIA_UNKNOWN, Environment.getStorageState(path));
+ assertEquals(Environment.MEDIA_UNKNOWN, Environment.getExternalStorageState(path));
}
/**
@@ -202,11 +198,11 @@
*/
public void testMountStatus() {
assertEquals(Environment.MEDIA_UNKNOWN,
- Environment.getStorageState(new File("/meow-should-never-exist")));
+ Environment.getExternalStorageState(new File("/meow-should-never-exist")));
// Internal data isn't a mount point
assertEquals(Environment.MEDIA_UNKNOWN,
- Environment.getStorageState(getContext().getCacheDir()));
+ Environment.getExternalStorageState(getContext().getCacheDir()));
}
/**
@@ -220,7 +216,7 @@
for (File path : paths) {
assertNotNull("Valid media must be inserted during CTS", path);
assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
- Environment.getStorageState(path));
+ Environment.getExternalStorageState(path));
assertTrue(path.getAbsolutePath().contains(packageName));
@@ -231,7 +227,7 @@
}
// Keep walking up until we leave device
- while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+ while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
assertDirReadOnlyAccess(path);
path = path.getParentFile();
}
@@ -250,12 +246,12 @@
for (File path : paths) {
assertNotNull("Valid media must be inserted during CTS", path);
assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
- Environment.getStorageState(path));
+ Environment.getExternalStorageState(path));
final File start = path;
boolean found = false;
- while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+ while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
final File test = new File(path, ".nomedia");
if (test.exists()) {
found = true;
@@ -301,33 +297,4 @@
probe.delete();
}
}
-
- /**
- * Leave gifts for other packages in their primary external cache dirs.
- */
- public void doWriteGifts() throws Exception {
- final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
- none.getParentFile().mkdirs();
- none.createNewFile();
- assertFileReadWriteAccess(none);
-
- writeInt(none, 100);
- assertEquals(100, readInt(none));
-
- final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
- read.getParentFile().mkdirs();
- read.createNewFile();
- assertFileReadWriteAccess(read);
-
- writeInt(read, 101);
- assertEquals(101, readInt(read));
-
- final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
- write.getParentFile().mkdirs();
- write.createNewFile();
- assertFileReadWriteAccess(write);
-
- writeInt(write, 102);
- assertEquals(102, readInt(write));
- }
}
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
new file mode 100644
index 0000000..5da42da
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.writeexternalstorageapp;
+
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.writeInt;
+
+import android.test.AndroidTestCase;
+
+import java.io.File;
+
+public class WriteGiftTest extends AndroidTestCase {
+ /**
+ * Leave gifts for other packages in their primary external cache dirs.
+ */
+ public void testGifts() throws Exception {
+ final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
+ none.getParentFile().mkdirs();
+ none.createNewFile();
+ assertFileReadWriteAccess(none);
+
+ writeInt(none, 100);
+ assertEquals(100, readInt(none));
+
+ final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
+ read.getParentFile().mkdirs();
+ read.createNewFile();
+ assertFileReadWriteAccess(read);
+
+ writeInt(read, 101);
+ assertEquals(101, readInt(read));
+
+ final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
+ write.getParentFile().mkdirs();
+ write.createNewFile();
+ assertFileReadWriteAccess(write);
+
+ writeInt(write, 102);
+ assertEquals(102, readInt(write));
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiConfigLockdownTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiConfigLockdownTest.java
index ef1d8f7..1627961 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiConfigLockdownTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiConfigLockdownTest.java
@@ -26,15 +26,15 @@
import java.util.List;
-import static com.android.compatibility.common.util.WifiConfigCreator.CREATE_WIFI_CONFIG_ACTION;
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_CREATE_WIFI_CONFIG;
import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_NETID;
import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_PASSWORD;
import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_SECURITY_TYPE;
import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_SSID;
-import static com.android.compatibility.common.util.WifiConfigCreator.REMOVE_WIFI_CONFIG_ACTION;
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_REMOVE_WIFI_CONFIG;
import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_NONE;
import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_WPA;
-import static com.android.compatibility.common.util.WifiConfigCreator.UPDATE_WIFI_CONFIG_ACTION;
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_UPDATE_WIFI_CONFIG;
/**
* Testing WiFi configuration lockdown by Device Owner
@@ -58,7 +58,7 @@
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, "1");
mConfigCreator.addNetwork(ORIGINAL_DEVICE_OWNER_SSID, true, SECURITY_TYPE_WPA,
ORIGINAL_PASSWORD);
- startRegularActivity(CREATE_WIFI_CONFIG_ACTION, -1, ORIGINAL_REGULAR_SSID,
+ startRegularActivity(ACTION_CREATE_WIFI_CONFIG, -1, ORIGINAL_REGULAR_SSID,
SECURITY_TYPE_WPA, ORIGINAL_PASSWORD);
}
@@ -116,7 +116,7 @@
int updateCount = 0;
for (WifiConfiguration config : configs) {
if (areMatchingSsids(ORIGINAL_DEVICE_OWNER_SSID, config.SSID)) {
- startRegularActivity(UPDATE_WIFI_CONFIG_ACTION, config.networkId,
+ startRegularActivity(ACTION_UPDATE_WIFI_CONFIG, config.networkId,
CHANGED_DEVICE_OWNER_SSID, SECURITY_TYPE_NONE, null);
++updateCount;
}
@@ -142,7 +142,7 @@
int removeCount = 0;
for (WifiConfiguration config : configs) {
if (areMatchingSsids(ORIGINAL_DEVICE_OWNER_SSID, config.SSID)) {
- startRegularActivity(REMOVE_WIFI_CONFIG_ACTION, config.networkId,
+ startRegularActivity(ACTION_REMOVE_WIFI_CONFIG, config.networkId,
null, SECURITY_TYPE_NONE, null);
++removeCount;
}
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
index 36ac7a7..2a24f4d 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
@@ -32,6 +32,29 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <activity android:name=".SimpleIntentReceiverActivity" android:exported="true"/>
+
+ <activity-alias android:name=".BrowserActivity"
+ android:targetActivity=".SimpleIntentReceiverActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <data android:scheme="http"/>
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias android:name=".AppLinkActivity"
+ android:targetActivity=".SimpleIntentReceiverActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <data android:scheme="http" android:host="com.android.cts.intent.receiver"/>
+ </intent-filter>
+ </activity-alias>
+
</application>
</manifest>
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
new file mode 100644
index 0000000..23755df
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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.intent.receiver;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import android.os.Bundle;
+
+/**
+ * An activity that receives an intent and returns immediately, indicating its own name and if it is
+ * running in a managed profile.
+ */
+public class SimpleIntentReceiverActivity extends Activity {
+ private static final String TAG = "SimpleIntentReceiverActivity";
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String className = getIntent().getComponent().getClassName();
+
+ // We try to check if we are in a managed profile or not.
+ // To do this, check if com.android.cts.managedprofile is the profile owner.
+ DevicePolicyManager dpm =
+ (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+ boolean inManagedProfile = dpm.isProfileOwnerApp("com.android.cts.managedprofile");
+
+ Log.i(TAG, "activity " + className + " started, is in managed profile: "
+ + inManagedProfile);
+ Intent result = new Intent();
+ result.putExtra("extra_receiver_class", className);
+ result.putExtra("extra_in_managed_profile", inManagedProfile);
+ setResult(Activity.RESULT_OK, result);
+ finish();
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
new file mode 100644
index 0000000..51ff362
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.intent.sender;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+public class AppLinkTest extends InstrumentationTestCase {
+
+ private static final String TAG = "AppLinkTest";
+
+ private Context mContext;
+ private IntentSenderActivity mActivity;
+
+ private static final String EXTRA_IN_MANAGED_PROFILE = "extra_in_managed_profile";
+ private static final String EXTRA_RECEIVER_CLASS = "extra_receiver_class";
+ private static final String APP_LINK_ACTIVITY
+ = "com.android.cts.intent.receiver.AppLinkActivity";
+ private static final String BROWSER_ACTIVITY
+ = "com.android.cts.intent.receiver.BrowserActivity";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getTargetContext();
+ mActivity = launchActivity(mContext.getPackageName(), IntentSenderActivity.class, null);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mActivity.finish();
+ super.tearDown();
+ }
+
+ public void testReceivedByAppLinkActivityInPrimary() throws Exception {
+ checkHttpIntentResult(APP_LINK_ACTIVITY, false);
+ }
+
+ public void testReceivedByAppLinkActivityInManaged() throws Exception {
+ checkHttpIntentResult(APP_LINK_ACTIVITY, true);
+ }
+
+ public void testReceivedByBrowserActivityInManaged() throws Exception {
+ checkHttpIntentResult(BROWSER_ACTIVITY, true);
+ }
+
+ public void testTwoReceivers() {
+ assertNumberOfReceivers(2);
+ }
+
+ public void testThreeReceivers() {
+ assertNumberOfReceivers(3);
+ }
+
+ // Should not be called if there are several possible receivers to the intent
+ // (see getHttpIntent)
+ private void checkHttpIntentResult(String receiverClassName, boolean inManagedProfile)
+ throws Exception {
+ PackageManager pm = mContext.getPackageManager();
+
+ Intent result = mActivity.getResult(getHttpIntent());
+ // If it is received in the other profile, we cannot check the class from the ResolveInfo
+ // returned by queryIntentActivities. So we rely on the receiver telling us its class.
+ assertEquals(receiverClassName, result.getStringExtra(EXTRA_RECEIVER_CLASS));
+ assertTrue(result.hasExtra(EXTRA_IN_MANAGED_PROFILE));
+ assertEquals(inManagedProfile, result.getBooleanExtra(EXTRA_IN_MANAGED_PROFILE, false));
+ }
+
+ private void assertNumberOfReceivers(int n) {
+ PackageManager pm = mContext.getPackageManager();
+ assertEquals(n, pm.queryIntentActivities(getHttpIntent(), /* flags = */ 0).size());
+ }
+
+ private Intent getHttpIntent() {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.addCategory(Intent.CATEGORY_BROWSABLE);
+ i.setData(Uri.parse("http://com.android.cts.intent.receiver"));
+ return i;
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
index fd421ac..eb64d47 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
@@ -66,8 +66,12 @@
}
public Intent getResult(Intent intent) throws Exception {
+ Log.d(TAG, "Sending intent " + intent);
startActivityForResult(intent, 42);
final Result result = mResult.poll(30, TimeUnit.SECONDS);
+ if (result != null) {
+ Log.d(TAG, "Result intent: " + result.data);
+ }
return (result != null) ? result.data : null;
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
index f4adb31..b31e74b 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -26,7 +26,8 @@
LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
-LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner compatibility-device-util_v2 \
+ ub-uiautomator
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 31e4ad4..e03ebdc 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -18,6 +18,7 @@
package="com.android.cts.managedprofile">
<uses-sdk android:minSdkVersion="20"/>
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
@@ -51,12 +52,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <activity android:name=".ComponentDisablingActivity" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT"/>
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
+ <activity android:name=".ComponentDisablingActivity" android:exported="true">
</activity>
<activity android:name=".ManagedProfileActivity">
<intent-filter>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
index 2a54d97..49754d0 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
@@ -19,7 +19,8 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
-import android.test.AndroidTestCase;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
/**
* Base class for profile-owner based tests.
@@ -27,7 +28,7 @@
* This class handles making sure that the test is the profile owner and that it has an active admin
* registered, so that all tests may assume these are done.
*/
-public class BaseManagedProfileTest extends AndroidTestCase {
+public class BaseManagedProfileTest extends InstrumentationTestCase {
public static class BasicAdminReceiver extends DeviceAdminReceiver {
}
@@ -36,21 +37,23 @@
BasicAdminReceiver.class.getPackage().getName(), BasicAdminReceiver.class.getName());
protected DevicePolicyManager mDevicePolicyManager;
+ protected Context mContext;
@Override
protected void setUp() throws Exception {
super.setUp();
+ mContext = getInstrumentation().getContext();
- mDevicePolicyManager = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- assertNotNull(mDevicePolicyManager);
+ mDevicePolicyManager = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ assertNotNull(mDevicePolicyManager);
- // TODO: Only check the below if we are running as the profile user. If running under the
- // user owner, can we check that there is a profile and that the below holds for it? If we
- // don't want to do these checks every time we could get rid of this class altogether and
- // just have a single test case running under the profile user that do them.
- assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
- assertTrue(mDevicePolicyManager.isProfileOwnerApp(
- ADMIN_RECEIVER_COMPONENT.getPackageName()));
+ // TODO: Only check the below if we are running as the profile user. If running under the
+ // user owner, can we check that there is a profile and that the below holds for it? If we
+ // don't want to do these checks every time we could get rid of this class altogether and
+ // just have a single test case running under the profile user that do them.
+ assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
+ assertTrue(mDevicePolicyManager.isProfileOwnerApp(
+ ADMIN_RECEIVER_COMPONENT.getPackageName()));
}
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
index 21b2d36..6a63cea 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
@@ -20,9 +20,16 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
import android.os.UserManager;
import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.List;
/**
* The methods in this class are not really tests.
@@ -31,6 +38,8 @@
* device-side methods from the host.
*/
public class CrossProfileUtils extends AndroidTestCase {
+ private static final String TAG = "CrossProfileUtils";
+
private static final String ACTION_READ_FROM_URI = "com.android.cts.action.READ_FROM_URI";
private static final String ACTION_WRITE_TO_URI = "com.android.cts.action.WRITE_TO_URI";
@@ -86,4 +95,18 @@
dpm.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
}
+
+ // Disables all browsers in current user
+ public void testDisableAllBrowsers() {
+ PackageManager pm = (PackageManager) getContext().getPackageManager();
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+ Intent webIntent = new Intent(Intent.ACTION_VIEW);
+ webIntent.setData(Uri.parse("http://com.android.cts.intent.receiver"));
+ List<ResolveInfo> ris = pm.queryIntentActivities(webIntent, 0 /* no flags*/);
+ for (ResolveInfo ri : ris) {
+ Log.d(TAG, "Hiding " + ri.activityInfo.packageName);
+ dpm.setApplicationHidden(ADMIN_RECEIVER_COMPONENT, ri.activityInfo.packageName, true);
+ }
+ }
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java
index 727b47f..7ddf77f 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java
@@ -23,6 +23,12 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.UserManager;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiWatcher;
+import android.support.test.uiautomator.Until;
import android.util.Log;
import java.util.concurrent.ArrayBlockingQueue;
@@ -54,9 +60,18 @@
private static final String EXTRA_GRANT_STATE
= "com.android.cts.permission.extra.GRANT_STATE";
private static final int PERMISSION_ERROR = -2;
+ private static final BySelector CRASH_POPUP_BUTTON_SELECTOR = By
+ .clazz(android.widget.Button.class.getName())
+ .text("OK")
+ .pkg("android");
+ private static final BySelector CRASH_POPUP_TEXT_SELECTOR = By
+ .clazz(android.widget.TextView.class.getName())
+ .pkg("android");
+ private static final String CRASH_WATCHER_ID = "CRASH";
private PermissionBroadcastReceiver mReceiver;
private PackageManager mPackageManager;
+ private UiDevice mDevice;
@Override
protected void setUp() throws Exception {
@@ -69,11 +84,13 @@
mReceiver = new PermissionBroadcastReceiver();
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PERMISSION_RESULT));
mPackageManager = mContext.getPackageManager();
+ mDevice = UiDevice.getInstance(getInstrumentation());
}
@Override
protected void tearDown() throws Exception {
mContext.unregisterReceiver(mReceiver);
+ mDevice.removeWatcher(CRASH_WATCHER_ID);
super.tearDown();
}
@@ -144,6 +161,28 @@
assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
}
+ public void testPermissionPrompts() throws Exception {
+ // register a crash watcher
+ mDevice.registerWatcher(CRASH_WATCHER_ID, new UiWatcher() {
+ @Override
+ public boolean checkForCondition() {
+ UiObject2 button = mDevice.findObject(CRASH_POPUP_BUTTON_SELECTOR);
+ if (button != null) {
+ UiObject2 text = mDevice.findObject(CRASH_POPUP_TEXT_SELECTOR);
+ Log.d(TAG, "Removing an error dialog: " + text != null ? text.getText() : null);
+ button.click();
+ return true;
+ }
+ return false;
+ }
+ });
+ mDevice.runWatchers();
+
+ assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
+ assertPermissionRequest(PackageManager.PERMISSION_DENIED, "permission_deny_button");
+ assertPermissionRequest(PackageManager.PERMISSION_GRANTED, "permission_allow_button");
+ }
+
public void testPermissionUpdate_setDeniedState() throws Exception {
assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
@@ -192,13 +231,18 @@
}
private void assertPermissionRequest(int expected) throws Exception {
+ assertPermissionRequest(expected, null);
+ }
+
+ private void assertPermissionRequest(int expected, String buttonResource) throws Exception {
Intent launchIntent = new Intent();
launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
PERMISSIONS_ACTIVITY_NAME));
launchIntent.putExtra(EXTRA_PERMISSION, PERMISSION_NAME);
launchIntent.setAction(ACTION_REQUEST_PERMISSION);
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
mContext.startActivity(launchIntent);
+ pressPermissionPromptButton(buttonResource);
assertEquals(expected, mReceiver.waitForBroadcast());
assertEquals(expected, mPackageManager.checkPermission(PERMISSION_NAME,
PERMISSION_APP_PACKAGE_NAME));
@@ -212,7 +256,7 @@
PERMISSIONS_ACTIVITY_NAME));
launchIntent.putExtra(EXTRA_PERMISSION, PERMISSION_NAME);
launchIntent.setAction(ACTION_CHECK_HAS_PERMISSION);
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
mContext.startActivity(launchIntent);
assertEquals(expected, mReceiver.waitForBroadcast());
}
@@ -246,6 +290,20 @@
PackageManager.PERMISSION_GRANTED);
}
+ private void pressPermissionPromptButton(String resName) throws Exception {
+ if (resName == null) {
+ return;
+ }
+
+ BySelector selector = By
+ .clazz(android.widget.Button.class.getName())
+ .res("com.android.packageinstaller", resName);
+ mDevice.wait(Until.hasObject(selector), 5000);
+ UiObject2 button = mDevice.findObject(selector);
+ assertNotNull("Couldn't find button with resource id: " + resName, button);
+ button.click();
+ }
+
private class PermissionBroadcastReceiver extends BroadcastReceiver {
private BlockingQueue<Integer> mQueue = new ArrayBlockingQueue<Integer> (1);
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
new file mode 100644
index 0000000..c4c02e8
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.managedprofile;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+
+import java.util.concurrent.TimeUnit;
+
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_CREATE_WIFI_CONFIG;
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_REMOVE_WIFI_CONFIG;
+import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_NETID;
+import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_SSID;
+
+/**
+ * Driven by the host-side test: com.android.cts.devicepolicy.ManagedProfileTest
+ *
+ * Each of these tests can run independently but have side-effects. The side-effects are used as
+ * building blocks to test various cleanup routines, for example that networks belonging to one
+ * user are deleted
+ */
+public class WifiTest extends AndroidTestCase {
+ private static final String TAG = WifiTest.class.getSimpleName();
+
+ // Unique SSID to use for this test (max SSID length is 32)
+ private static final String NETWORK_SSID = "com.android.cts.xwde7ktvh8rmjuhr";
+
+ // Time duration to allow before assuming that a WiFi operation failed and ceasing to wait.
+ private static final long UPDATE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30);
+ private static final long UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);
+
+ // Shared WifiManager instance.
+ private WifiManager mWifiManager;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+ }
+
+ /**
+ * Add a network through the WifiManager API. Verifies that the network was actually added.
+ *
+ * <p>Side effects:
+ * <ul>
+ * <li>Network with SSID {@link WifiTest#NETWORK_SSID} is created.</li>
+ * </ul>
+ */
+ public void testAddWifiNetwork() throws Exception {
+ Intent intent = new Intent(ACTION_CREATE_WIFI_CONFIG);
+ intent.putExtra(EXTRA_SSID, NETWORK_SSID);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+
+ // Wait for configuration to appear in networks list.
+ assertTrue(awaitNetworkState(NETWORK_SSID, /* exists */ true));
+ }
+
+ /**
+ * Remove any network through the WifiManager API with a certain SSID. Verifies that the network
+ * was actually removed.
+ *
+ * <p>Side effects:
+ * <ul>
+ * <li>If a network with SSID {@link WifiTest#NETWORK_SSID} exists, it will be deleted.</li>
+ * </ul>
+ */
+ public void testRemoveWifiNetworkIfExists() throws Exception {
+ WifiConfiguration config = getNetworkForSsid(NETWORK_SSID);
+
+ if (config != null && config.networkId != -1) {
+ Intent intent = new Intent(ACTION_REMOVE_WIFI_CONFIG);
+ intent.putExtra(EXTRA_NETID, config.networkId);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ }
+
+ assertTrue(awaitNetworkState(NETWORK_SSID, /* exists */ false));
+ }
+
+ /**
+ * Verify that no network exists with a certain SSID.
+ *
+ * <p>The SSID that will be checked for is {@link WifiTest#NETWORK_SSID}.
+ */
+ public void testWifiNetworkDoesNotExist() throws Exception {
+ assertTrue(awaitNetworkState(NETWORK_SSID, /* exists */ false));
+ }
+
+ /**
+ * Block until a network configuration with a certain SSID either exists or ceases to.
+ * Wait for up to {@link WifiTest#UPDATE_TIMEOUT_MS} milliseconds, in increments of
+ * {@link WifiTest#UPDATE_INTERVAL_MS}.
+ */
+ private boolean awaitNetworkState(String ssid, boolean exists) {
+ for (int probes = 0; probes * UPDATE_INTERVAL_MS <= UPDATE_TIMEOUT_MS; probes++) {
+ if (probes != 0) {
+ SystemClock.sleep(UPDATE_INTERVAL_MS);
+ }
+ if ((getNetworkForSsid(ssid) != null) == exists) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Internal method to find an existing {@link WifiConfiguration} with the given SSID.
+ *
+ * @return A {@link WifiConfiguration} matching the specification, or {@code null} if no such
+ * configuration exists.
+ */
+ private WifiConfiguration getNetworkForSsid(String ssid) {
+ if (!ssid.startsWith("\"")) {
+ ssid = '"' + ssid + '"';
+ }
+ for (WifiConfiguration config : mWifiManager.getConfiguredNetworks()) {
+ if (ssid.equals(config.SSID)) {
+ return config;
+ }
+ }
+ return null;
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
index 76a9e44..9646e61 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
@@ -64,11 +64,4 @@
// Verify the profile is deleted
assertFalse(mUserManager.getUserProfiles().contains(currentUser));
}
-
- // Override this test inherited from base class, as it will trigger another round of setUp()
- // which would fail because the managed profile has been removed by this test.
- @Override
- @Ignore
- public void testAndroidTestCaseSetupProperly() {
- }
}
diff --git a/hostsidetests/devicepolicy/app/PermissionApp/src/com/android/cts/permission/permissionapp/PermissionActivity.java b/hostsidetests/devicepolicy/app/PermissionApp/src/com/android/cts/permission/permissionapp/PermissionActivity.java
index 700aae1..fc45635 100644
--- a/hostsidetests/devicepolicy/app/PermissionApp/src/com/android/cts/permission/permissionapp/PermissionActivity.java
+++ b/hostsidetests/devicepolicy/app/PermissionApp/src/com/android/cts/permission/permissionapp/PermissionActivity.java
@@ -47,14 +47,18 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
final Intent received = getIntent();
+ Log.d(TAG, "Started with " + received);
+
final String action = received.getAction();
mPermission = received.getStringExtra(EXTRA_PERMISSION);
if (ACTION_REQUEST_PERMISSION.equals(action)) {
+ Log.d(TAG, "Requesting permission " + mPermission);
requestPermissions(new String[] {mPermission}, PERMISSIONS_REQUEST_CODE);
} else if (ACTION_CHECK_HAS_PERMISSION.equals(action)) {
+ Log.d(TAG, "Checking permission " + mPermission);
sendResultBroadcast(checkSelfPermission(mPermission));
- finish();
} else {
Log.w(TAG, "Unknown intent received: " + received);
finish();
@@ -73,7 +77,6 @@
Log.d(TAG, "Received valid permission result: " + grantResults[0]);
sendResultBroadcast(grantResults[0]);
}
- finish();
}
private void sendResultBroadcast(int result) {
@@ -81,5 +84,6 @@
Intent broadcast = new Intent(ACTION_PERMISSION_RESULT);
broadcast.putExtra(EXTRA_GRANT_STATE, result);
sendBroadcast(broadcast);
+ finish();
}
}
diff --git a/hostsidetests/devicepolicy/app/WifiConfigCreator/src/com/android/cts/deviceowner/wificonfigcreator/WifiConfigCreatorActivity.java b/hostsidetests/devicepolicy/app/WifiConfigCreator/src/com/android/cts/deviceowner/wificonfigcreator/WifiConfigCreatorActivity.java
index 7958cb1..af31030 100644
--- a/hostsidetests/devicepolicy/app/WifiConfigCreator/src/com/android/cts/deviceowner/wificonfigcreator/WifiConfigCreatorActivity.java
+++ b/hostsidetests/devicepolicy/app/WifiConfigCreator/src/com/android/cts/deviceowner/wificonfigcreator/WifiConfigCreatorActivity.java
@@ -22,14 +22,14 @@
import android.util.Log;
import com.android.compatibility.common.util.WifiConfigCreator;
-import static com.android.compatibility.common.util.WifiConfigCreator.CREATE_WIFI_CONFIG_ACTION;
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_CREATE_WIFI_CONFIG;
import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_NETID;
import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_PASSWORD;
import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_SECURITY_TYPE;
import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_SSID;
-import static com.android.compatibility.common.util.WifiConfigCreator.REMOVE_WIFI_CONFIG_ACTION;
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_REMOVE_WIFI_CONFIG;
import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_NONE;
-import static com.android.compatibility.common.util.WifiConfigCreator.UPDATE_WIFI_CONFIG_ACTION;
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_UPDATE_WIFI_CONFIG;
/**
* A simple activity to create and manage wifi configurations.
@@ -45,18 +45,18 @@
try {
Intent intent = getIntent();
String action = intent.getAction();
- if (CREATE_WIFI_CONFIG_ACTION.equals(action)) {
+ if (ACTION_CREATE_WIFI_CONFIG.equals(action)) {
String ssid = intent.getStringExtra(EXTRA_SSID);
int securityType = intent.getIntExtra(EXTRA_SECURITY_TYPE, SECURITY_TYPE_NONE);
String password = intent.getStringExtra(EXTRA_PASSWORD);
configCreator.addNetwork(ssid, false, securityType, password);
- } else if (UPDATE_WIFI_CONFIG_ACTION.equals(action)) {
+ } else if (ACTION_UPDATE_WIFI_CONFIG.equals(action)) {
int netId = intent.getIntExtra(EXTRA_NETID, -1);
String ssid = intent.getStringExtra(EXTRA_SSID);
int securityType = intent.getIntExtra(EXTRA_SECURITY_TYPE, SECURITY_TYPE_NONE);
String password = intent.getStringExtra(EXTRA_PASSWORD);
configCreator.updateNetwork(netId, ssid, false, securityType, password);
- } else if (REMOVE_WIFI_CONFIG_ACTION.equals(action)) {
+ } else if (ACTION_REMOVE_WIFI_CONFIG.equals(action)) {
int netId = intent.getIntExtra(EXTRA_NETID, -1);
if (netId != -1) {
configCreator.removeNetwork(netId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 374f2f9..de7c16b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -103,8 +103,6 @@
CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
assertTrue(commandOutput + " expected to start with \"Success:\"",
commandOutput.startsWith("Success:"));
- // Wait 60 seconds for intents generated to be handled.
- Thread.sleep(60 * 1000);
}
protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
@@ -144,12 +142,14 @@
}
protected void removeUser(int userId) throws Exception {
+ String stopUserCommand = "am stop-user -w " + userId;
+ CLog.logAndDisplay(LogLevel.INFO, "starting command \"" + stopUserCommand + "\" and waiting.");
+ CLog.logAndDisplay(LogLevel.INFO, "Output for command " + stopUserCommand + ": "
+ + getDevice().executeShellCommand(stopUserCommand));
String removeUserCommand = "pm remove-user " + userId;
CLog.logAndDisplay(LogLevel.INFO, "starting command " + removeUserCommand);
CLog.logAndDisplay(LogLevel.INFO, "Output for command " + removeUserCommand + ": "
+ getDevice().executeShellCommand(removeUserCommand));
- // Wait 60 seconds for user to finish being removed.
- Thread.sleep(60 * 1000);
}
protected void removeTestUsers() throws Exception {
@@ -288,8 +288,6 @@
String[] tokens = commandOutput.split("\\s+");
assertTrue(tokens.length > 0);
assertEquals("Success:", tokens[0]);
- // Wait 60 seconds for intents generated to be handled.
- Thread.sleep(60 * 1000);
return Integer.parseInt(tokens[tokens.length-1]);
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 15f5368..52e1e75 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -45,11 +45,21 @@
private static final String CERT_INSTALLER_PKG = "com.android.cts.certinstaller";
private static final String CERT_INSTALLER_APK = "CtsCertInstallerApp.apk";
+ private static final String WIFI_CONFIG_CREATOR_PKG = "com.android.cts.wificonfigcreator";
+ private static final String WIFI_CONFIG_CREATOR_APK = "CtsWifiConfigCreator.apk";
+
private static final String ADMIN_RECEIVER_TEST_CLASS =
MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
private static final String FEATURE_CAMERA = "android.hardware.camera";
+ private static final String FEATURE_WIFI = "android.hardware.wifi";
+
+ private static final String ADD_RESTRICTION_COMMAND = "add-restriction";
+
+ private static final int USER_OWNER = 0;
+
+ // ID of the profile we'll create. This will always be a profile of USER_OWNER.
private int mUserId;
private String mPackageVerifier;
@@ -127,6 +137,29 @@
}
}
+ /**
+ * Verify that removing a managed profile will remove all networks owned by that profile.
+ */
+ public void testProfileWifiCleanup() throws Exception {
+ if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
+ return;
+ }
+ assertTrue("WiFi config already exists and could not be removed", runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG, ".WifiTest", "testRemoveWifiNetworkIfExists", USER_OWNER));
+ try {
+ installApp(WIFI_CONFIG_CREATOR_APK);
+ assertTrue("Failed to add WiFi config", runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG, ".WifiTest", "testAddWifiNetwork", mUserId));
+
+ // Now delete the user - should undo the effect of testAddWifiNetwork.
+ removeUser(mUserId);
+ assertTrue("WiFi config not removed after deleting profile", runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG, ".WifiTest", "testWifiNetworkDoesNotExist", USER_OWNER));
+ } finally {
+ getDevice().uninstallPackage(WIFI_CONFIG_CREATOR_APK);
+ }
+ }
+
public void testCrossProfileIntentFilters() throws Exception {
if (!mHasFeature) {
return;
@@ -148,6 +181,49 @@
// TODO: Test with startActivity
}
+ public void testAppLinks() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ // Disable all pre-existing browsers in the managed profile so they don't interfere with
+ // intents resolution.
+ assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ "testDisableAllBrowsers", mUserId));
+ installApp(INTENT_RECEIVER_APK);
+ installApp(INTENT_SENDER_APK);
+
+ changeVerificationStatus(USER_OWNER, INTENT_RECEIVER_PKG, "ask");
+ changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "ask");
+ // We should have two receivers: IntentReceiverActivity and BrowserActivity in the
+ // managed profile
+ assertAppLinkResult("testTwoReceivers");
+
+ changeUserRestrictionForUser("allow_parent_profile_app_linking", ADD_RESTRICTION_COMMAND,
+ mUserId);
+ // Now we should also have one receiver in the primary user, so three receivers in total.
+ assertAppLinkResult("testThreeReceivers");
+
+ changeVerificationStatus(USER_OWNER, INTENT_RECEIVER_PKG, "never");
+ // The primary user one has been set to never: we should only have the managed profile ones.
+ assertAppLinkResult("testTwoReceivers");
+
+ changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "never");
+ // Now there's only the browser in the managed profile left
+ assertAppLinkResult("testReceivedByBrowserActivityInManaged");
+
+ changeVerificationStatus(USER_OWNER, INTENT_RECEIVER_PKG, "always");
+ changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "ask");
+ // We've set the receiver in the primary user to always: only this one should receive the
+ // intent.
+ assertAppLinkResult("testReceivedByAppLinkActivityInPrimary");
+
+ changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "always");
+ // We have one always in the primary user and one always in the managed profile: the managed
+ // profile one should have precedence.
+ assertAppLinkResult("testReceivedByAppLinkActivityInManaged");
+ }
+
+
public void testSettingsIntents() throws Exception {
if (!mHasFeature) {
return;
@@ -238,17 +314,16 @@
return;
}
String restriction = "no_debugging_features"; // UserManager.DISALLOW_DEBUGGING_FEATURES
- String command = "add-restriction";
String addRestrictionCommandOutput =
- changeUserRestrictionForUser(restriction, command, mUserId);
+ changeUserRestrictionForUser(restriction, ADD_RESTRICTION_COMMAND, mUserId);
assertTrue("Command was expected to succeed " + addRestrictionCommandOutput,
addRestrictionCommandOutput.contains("Status: ok"));
// This should now fail, as the shell is not available to start activities under a different
// user once the restriction is in place.
addRestrictionCommandOutput =
- changeUserRestrictionForUser(restriction, command, mUserId);
+ changeUserRestrictionForUser(restriction, ADD_RESTRICTION_COMMAND, mUserId);
assertTrue(
"Expected SecurityException when starting the activity "
+ addRestrictionCommandOutput,
@@ -501,6 +576,22 @@
"testPermissionMixedPolicies", mUserId));
}
+ public void testPermissionPrompts() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ try {
+ // unlock device and ensure that the screen stays on
+ getDevice().executeShellCommand("input keyevent 82");
+ getDevice().executeShellCommand("settings put global stay_on_while_plugged_in 2");
+ installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+ assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
+ "testPermissionPrompts", mUserId));
+ } finally {
+ getDevice().executeShellCommand("settings put global stay_on_while_plugged_in 0");
+ }
+ }
+
public void testPermissionAppUpdate() throws Exception {
if (!mHasFeature) {
return;
@@ -576,4 +667,16 @@
"Output for command " + adbCommand + ": " + commandOutput);
return commandOutput;
}
+
+ // status should be one of never, undefined, ask, always
+ private void changeVerificationStatus(int userId, String packageName, String status)
+ throws DeviceNotAvailableException {
+ String command = "pm set-app-link --user " + userId + " " + packageName + " " + status;
+ CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": "
+ + getDevice().executeShellCommand(command));
+ }
+
+ private void assertAppLinkResult(String methodName) throws DeviceNotAvailableException {
+ assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".AppLinkTest", methodName, mUserId));
+ }
}
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java
index d070972..a0016e2 100644
--- a/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java
@@ -16,8 +16,6 @@
package com.android.cts.monkey;
-import com.android.tradefed.log.LogUtil.CLog;
-
import java.util.Scanner;
public class SeedTest extends AbstractMonkeyTest {
@@ -26,15 +24,11 @@
String cmd1 = MONKEY_CMD + " -s 1337 -v -p " + PKGS[0] + " 500";
String out1 = mDevice.executeShellCommand(cmd1);
String out2 = mDevice.executeShellCommand(cmd1);
- CLog.d("monkey output1: %s", out1);
- CLog.d("monkey output2: %s", out2);
assertOutputs(out1, out2);
String cmd2 = MONKEY_CMD + " -s 3007 -v -p " + PKGS[0] + " 125";
String out3 = mDevice.executeShellCommand(cmd2);
String out4 = mDevice.executeShellCommand(cmd2);
- CLog.d("monkey output3: %s", out3);
- CLog.d("monkey output4: %s", out4);
assertOutputs(out3, out4);
}
diff --git a/hostsidetests/usage/Android.mk b/hostsidetests/usage/Android.mk
new file mode 100644
index 0000000..917528b
--- /dev/null
+++ b/hostsidetests/usage/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)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_MODULE := CtsUsageHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.host.app.usage
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+include $(call all-subdir-makefiles)
diff --git a/hostsidetests/usage/app/Android.mk b/hostsidetests/usage/app/Android.mk
new file mode 100644
index 0000000..b23efbc
--- /dev/null
+++ b/hostsidetests/usage/app/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsDeviceAppUsageTestApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/usage/app/AndroidManifest.xml b/hostsidetests/usage/app/AndroidManifest.xml
new file mode 100755
index 0000000..bad453f
--- /dev/null
+++ b/hostsidetests/usage/app/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.app.usage.test">
+
+ <application>
+ <activity android:name=".TestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java b/hostsidetests/usage/app/src/com/android/cts/app/usage/test/TestActivity.java
similarity index 67%
rename from tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
rename to hostsidetests/usage/app/src/com/android/cts/app/usage/test/TestActivity.java
index accdcaf..9432477 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
+++ b/hostsidetests/usage/app/src/com/android/cts/app/usage/test/TestActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 The Android Open Source Project
+ * 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.
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package android.hardware.input.cts;
+package com.android.cts.app.usage.test;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
+import android.app.Activity;
-public interface InputCallback {
- public void onKeyEvent(KeyEvent ev);
- public void onMotionEvent(MotionEvent ev);
-}
+public class TestActivity extends Activity {
+}
\ No newline at end of file
diff --git a/hostsidetests/usage/src/com/android/cts/app/usage/AppIdleHostTest.java b/hostsidetests/usage/src/com/android/cts/app/usage/AppIdleHostTest.java
new file mode 100644
index 0000000..f24be0e
--- /dev/null
+++ b/hostsidetests/usage/src/com/android/cts/app/usage/AppIdleHostTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.app.usage;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+public class AppIdleHostTest extends DeviceTestCase implements IBuildReceiver {
+ private static final String SETTINGS_APP_IDLE_CONSTANTS = "app_idle_constants";
+
+ private static final String TEST_APP_PACKAGE = "com.android.cts.app.usage.test";
+ private static final String TEST_APP_APK = "CtsDeviceAppUsageTestApp.apk";
+ private static final String TEST_APP_CLASS = "TestActivity";
+
+ private static final long ACTIVITY_LAUNCH_WAIT_MILLIS = 500;
+
+ /**
+ * A reference to the build.
+ */
+ private CtsBuildHelper mBuild;
+
+ /**
+ * A reference to the device under test.
+ */
+ private ITestDevice mDevice;
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ // Get the build, this is used to access the APK.
+ mBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Get the device, this gives a handle to run commands and install APKs.
+ mDevice = getDevice();
+
+ // Remove any previously installed versions of this APK.
+ mDevice.uninstallPackage(TEST_APP_PACKAGE);
+
+ // Install the APK on the device.
+ mDevice.installPackage(mBuild.getTestApp(TEST_APP_APK), false);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Remove the package once complete.
+ mDevice.uninstallPackage(TEST_APP_PACKAGE);
+ super.tearDown();
+ }
+
+ /**
+ * Checks whether an package is idle.
+ * @param appPackage The package to check for idleness.
+ * @return true if the package is idle
+ * @throws DeviceNotAvailableException
+ */
+ private boolean isAppIdle(String appPackage) throws DeviceNotAvailableException {
+ String result = mDevice.executeShellCommand(String.format("am get-inactive %s", appPackage));
+ return result.contains("Idle=true");
+ }
+
+ /**
+ * Set the app idle settings.
+ * @param settingsStr The settings string, a comma separated key=value list.
+ * @throws DeviceNotAvailableException
+ */
+ private void setAppIdleSettings(String settingsStr) throws DeviceNotAvailableException {
+ mDevice.executeShellCommand(String.format("settings put global %s \"%s\"",
+ SETTINGS_APP_IDLE_CONSTANTS, settingsStr));
+ }
+
+ /**
+ * Get the current app idle settings.
+ * @throws DeviceNotAvailableException
+ */
+ private String getAppIdleSettings() throws DeviceNotAvailableException {
+ String result = mDevice.executeShellCommand(String.format("settings get global %s",
+ SETTINGS_APP_IDLE_CONSTANTS));
+ return result.trim();
+ }
+
+ /**
+ * Launch the test app for a few hundred milliseconds then launch home.
+ * @throws DeviceNotAvailableException
+ */
+ private void startAndStopTestApp() throws DeviceNotAvailableException {
+ // Launch the app.
+ mDevice.executeShellCommand(
+ String.format("am start -W -a android.intent.action.MAIN -n %s/%s.%s",
+ TEST_APP_PACKAGE, TEST_APP_PACKAGE, TEST_APP_CLASS));
+
+ // Wait for some time.
+ sleepUninterrupted(ACTIVITY_LAUNCH_WAIT_MILLIS);
+
+ // Launch home.
+ mDevice.executeShellCommand(
+ "am start -W -a android.intent.action.MAIN -c android.intent.category.HOME");
+ }
+
+ /**
+ * Tests that the app is idle when the idle threshold is passed, and that the app is not-idle
+ * before the threshold is passed.
+ *
+ * @throws Exception
+ */
+ public void testAppIsIdle() throws Exception {
+ final String previousState = getAppIdleSettings();
+ try {
+ // Set the app idle time to be super low.
+ setAppIdleSettings("idle_duration=5,wallclock_threshold=5");
+ startAndStopTestApp();
+ assertTrue(isAppIdle(TEST_APP_PACKAGE));
+
+ // Set the app idle time to something larger.
+ setAppIdleSettings("idle_duration=10000,wallclock_threshold=10000");
+ startAndStopTestApp();
+ assertFalse(isAppIdle(TEST_APP_PACKAGE));
+ } finally {
+ setAppIdleSettings(previousState);
+ }
+ }
+
+ private static void sleepUninterrupted(long timeMillis) {
+ boolean interrupted;
+ do {
+ try {
+ Thread.sleep(timeMillis);
+ interrupted = false;
+ } catch (InterruptedException e) {
+ interrupted = true;
+ }
+ } while (interrupted);
+ }
+}
diff --git a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
index eb0e784..2553d60 100644
--- a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
+++ b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
@@ -37,6 +37,7 @@
import com.android.cts.util.ResultType;
import com.android.cts.util.ResultUnit;
import com.android.cts.util.Stat;
+import com.android.cts.util.TimeoutReq;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -223,18 +224,22 @@
doTestGoog(VIDEO_AVC, 720, 480);
}
+ @TimeoutReq(minutes = 10)
public void testAvc1280x0720Other() throws Exception {
doTestOther(VIDEO_AVC, 1280, 720);
}
+ @TimeoutReq(minutes = 10)
public void testAvc1280x0720Goog() throws Exception {
doTestGoog(VIDEO_AVC, 1280, 720);
}
+ @TimeoutReq(minutes = 10)
public void testAvc1920x1080Other() throws Exception {
doTestOther(VIDEO_AVC, 1920, 1080);
}
+ @TimeoutReq(minutes = 10)
public void testAvc1920x1080Goog() throws Exception {
doTestGoog(VIDEO_AVC, 1920, 1080);
}
@@ -256,18 +261,22 @@
doTestGoog(VIDEO_VP8, 640, 360);
}
+ @TimeoutReq(minutes = 10)
public void testVp81280x0720Other() throws Exception {
doTestOther(VIDEO_VP8, 1280, 720);
}
+ @TimeoutReq(minutes = 10)
public void testVp81280x0720Goog() throws Exception {
doTestGoog(VIDEO_VP8, 1280, 720);
}
+ @TimeoutReq(minutes = 10)
public void testVp81920x1080Other() throws Exception {
doTestOther(VIDEO_VP8, 1920, 1080);
}
+ @TimeoutReq(minutes = 10)
public void testVp81920x1080Goog() throws Exception {
doTestGoog(VIDEO_VP8, 1920, 1080);
}
@@ -314,10 +323,12 @@
doTestGoog(VIDEO_MPEG4, 640, 480);
}
+ @TimeoutReq(minutes = 10)
public void testMpeg41280x0720Other() throws Exception {
doTestOther(VIDEO_MPEG4, 1280, 720);
}
+ @TimeoutReq(minutes = 10)
public void testMpeg41280x0720Goog() throws Exception {
doTestGoog(VIDEO_MPEG4, 1280, 720);
}
@@ -351,7 +362,6 @@
private void doTestGoog(String mimeType, int w, int h) throws Exception {
mTestConfig.mTestPixels = false;
- mTestConfig.mReportFrameTime = true;
mTestConfig.mTotalFrames = 3000;
mTestConfig.mNumberOfRepeat = 2;
doTest(true /* isGoog */, mimeType, w, h);
@@ -360,7 +370,6 @@
private void doTestOther(String mimeType, int w, int h) throws Exception {
mTestConfig.mTestPixels = false;
mTestConfig.mTestResult = true;
- mTestConfig.mReportFrameTime = true;
mTestConfig.mTotalFrames = 3000;
mTestConfig.mNumberOfRepeat = 2;
doTest(false /* isGoog */, mimeType, w, h);
diff --git a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
index 19f19c0..57dc7c6 100644
--- a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
+++ b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
@@ -123,19 +123,27 @@
Log.d(TAG, "Free memory : " + free);
String tempdir = System.getProperty("java.io.tmpdir", "");
+ // TODO: Remove these extra Logs added to debug a specific timeout problem.
+ Log.d(TAG, "java.io.tmpdir is:" + tempdir);
+
if (!TextUtils.isEmpty(tempdir)) {
String[] commands = {"df", tempdir};
BufferedReader in = null;
try {
+ Log.d(TAG, "About to .exec df");
Process proc = runtime.exec(commands);
+ Log.d(TAG, ".exec returned");
in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
+ Log.d(TAG, "Stream reader created");
String line;
while ((line = in.readLine()) != null) {
Log.d(TAG, line);
}
} catch (IOException e) {
+ Log.d(TAG, "Exception: " + e.toString());
// Well, we tried
} finally {
+ Log.d(TAG, "In finally");
if (in != null) {
try {
in.close();
diff --git a/tests/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/tests/app/src/android/app/cts/DownloadManagerTest.java
index 8c0381a..55ad392 100644
--- a/tests/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -130,7 +130,7 @@
assertDownloadQueryableById(id);
- receiver.waitForDownloadComplete(SHORT_TIMEOUT, id);
+ receiver.waitForDownloadComplete(LONG_TIMEOUT, id);
assertDownloadQueryableByStatus(DownloadManager.STATUS_SUCCESSFUL);
@@ -158,7 +158,7 @@
assertDownloadQueryableById(id);
- receiver.waitForDownloadComplete(SHORT_TIMEOUT, id);
+ receiver.waitForDownloadComplete(LONG_TIMEOUT, id);
assertDownloadQueryableByStatus(DownloadManager.STATUS_SUCCESSFUL);
diff --git a/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
index 35466be..3024896 100644
--- a/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
+++ b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
@@ -26,7 +26,7 @@
*/
public class PrivateAttributeTest extends AndroidTestCase {
- private static final int sLastPublicAttr = 0x010104d5;
+ private static final int sLastPublicAttr = 0x010104f1;
public void testNoAttributesAfterLastPublicAttribute() throws Exception {
final Resources res = getContext().getResources();
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 1d1e3a8..031b19e 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -26,6 +26,7 @@
<uses-permission android:name="android.permission.BODY_SENSORS" />
<uses-permission android:name="android.permission.TRANSMIT_IR" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
+ <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<application>
<uses-library android:name="android.test.runner" />
@@ -71,8 +72,10 @@
android:process=":camera2ActivityProcess">
</activity>
- <activity android:name="android.hardware.input.cts.InputCtsActivity"
- android:label="InputCtsActivity" />
+ <activity android:name="android.hardware.cts.FingerprintTestActivity"
+ android:label="FingerprintTestActivity">
+ </activity>
+
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/hardware/res/raw/gamepad_press_a.json b/tests/tests/hardware/res/raw/gamepad_press_a.json
deleted file mode 100644
index ff3ca4f..0000000
--- a/tests/tests/hardware/res/raw/gamepad_press_a.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "id": 1,
- "command": "register",
- "name": "Odie (Test)",
- "vid": 0x18d1,
- "pid": 0x2c40,
- "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
- 0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
- 0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
- 0x23, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0b, 0x81, 0x02, 0x75, 0x01, 0x95,
- 0x01, 0x81, 0x03, 0x05, 0x01, 0x75, 0x04, 0x95, 0x01, 0x25, 0x07, 0x46, 0x3b, 0x01, 0x66,
- 0x14, 0x00, 0x09, 0x39, 0x81, 0x42, 0x66, 0x00, 0x00, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30,
- 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x05, 0x02, 0x09, 0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26,
- 0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0x85,
- 0x02, 0x05, 0x08, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x04, 0x00,
- 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x91, 0x02, 0x75, 0x04, 0x95, 0x01, 0x91,
- 0x03, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x03, 0x05, 0x01, 0x09, 0x06, 0xa1,
- 0x02, 0x05, 0x06, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x81,
- 0x02, 0x06, 0xbc, 0xff, 0x0a, 0xad, 0xbd, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0xc0],
- "report": [0x01, 0x00, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
-}
-
-{
- "id": 1,
- "command": "report",
- "report": [0x01, 0x01, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
-}
-
-{
- "id": 1,
- "command": "delay",
- "duration": 10
-}
-
-{
- "id": 1,
- "command": "report",
- "report": [0x01, 0x00, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
-}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index da083a6..f8ee615 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -64,7 +64,11 @@
private String[] mIds;
private CameraErrorCollector mCollector;
+ private static final Size FULLHD = new Size(1920, 1080);
+ private static final Size FULLHD_ALT = new Size(1920, 1088);
+ private static final Size HD = new Size(1280, 720);
private static final Size VGA = new Size(640, 480);
+ private static final Size QVGA = new Size(320, 240);
/*
* HW Levels short hand
@@ -149,14 +153,102 @@
assertArrayContains(String.format("No JPEG image format for: ID %s",
mIds[counter]), outputFormats, ImageFormat.JPEG);
- Size[] sizes = config.getOutputSizes(ImageFormat.YUV_420_888);
- CameraTestUtils.assertArrayNotEmpty(sizes,
+ Size[] yuvSizes = config.getOutputSizes(ImageFormat.YUV_420_888);
+ Size[] jpegSizes = config.getOutputSizes(ImageFormat.JPEG);
+ Size[] privateSizes = config.getOutputSizes(ImageFormat.PRIVATE);
+
+ CameraTestUtils.assertArrayNotEmpty(yuvSizes,
String.format("No sizes for preview format %x for: ID %s",
ImageFormat.YUV_420_888, mIds[counter]));
- assertArrayContains(String.format(
- "Required VGA size not found for format %x for: ID %s",
- ImageFormat.YUV_420_888, mIds[counter]), sizes, VGA);
+ Rect activeRect = CameraTestUtils.getValueNotNull(
+ c, CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ Size activeArraySize = new Size(activeRect.width(), activeRect.height());
+ Integer hwLevel = c.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+
+ if (activeArraySize.getWidth() >= FULLHD.getWidth() &&
+ activeArraySize.getHeight() >= FULLHD.getHeight()) {
+ assertArrayContains(String.format(
+ "Required FULLHD size not found for format %x for: ID %s",
+ ImageFormat.JPEG, mIds[counter]), jpegSizes, FULLHD);
+ }
+
+ if (activeArraySize.getWidth() >= HD.getWidth() &&
+ activeArraySize.getHeight() >= HD.getHeight()) {
+ assertArrayContains(String.format(
+ "Required HD size not found for format %x for: ID %s",
+ ImageFormat.JPEG, mIds[counter]), jpegSizes, HD);
+ }
+
+ if (activeArraySize.getWidth() >= VGA.getWidth() &&
+ activeArraySize.getHeight() >= VGA.getHeight()) {
+ assertArrayContains(String.format(
+ "Required VGA size not found for format %x for: ID %s",
+ ImageFormat.JPEG, mIds[counter]), jpegSizes, VGA);
+ }
+
+ if (activeArraySize.getWidth() >= QVGA.getWidth() &&
+ activeArraySize.getHeight() >= QVGA.getHeight()) {
+ assertArrayContains(String.format(
+ "Required QVGA size not found for format %x for: ID %s",
+ ImageFormat.JPEG, mIds[counter]), jpegSizes, QVGA);
+ }
+
+ ArrayList<Size> jpegSizesList = new ArrayList<>(Arrays.asList(jpegSizes));
+ ArrayList<Size> yuvSizesList = new ArrayList<>(Arrays.asList(yuvSizes));
+ ArrayList<Size> privateSizesList = new ArrayList<>(Arrays.asList(privateSizes));
+
+ CamcorderProfile maxVideoProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
+ Size maxVideoSize = new Size(
+ maxVideoProfile.videoFrameWidth, maxVideoProfile.videoFrameHeight);
+
+ // Handle FullHD special case first
+ if (jpegSizesList.contains(FULLHD)) {
+ if (hwLevel == FULL || (hwLevel == LIMITED &&
+ maxVideoSize.getWidth() >= FULLHD.getWidth() &&
+ maxVideoSize.getHeight() >= FULLHD.getHeight())) {
+ boolean yuvSupportFullHD = yuvSizesList.contains(FULLHD) ||
+ yuvSizesList.contains(FULLHD_ALT);
+ boolean privateSupportFullHD = privateSizesList.contains(FULLHD) ||
+ privateSizesList.contains(FULLHD_ALT);
+ assertTrue("Full device FullHD YUV size not found", yuvSupportFullHD);
+ assertTrue("Full device FullHD PRIVATE size not found", privateSupportFullHD);
+ }
+ // remove all FullHD or FullHD_Alt sizes for the remaining of the test
+ jpegSizesList.remove(FULLHD);
+ jpegSizesList.remove(FULLHD_ALT);
+ }
+
+ // Check all sizes other than FullHD
+ if (hwLevel == LIMITED) {
+ // Remove all jpeg sizes larger than max video size
+ ArrayList<Size> toBeRemoved = new ArrayList<>();
+ for (Size size : jpegSizesList) {
+ if (size.getWidth() >= maxVideoSize.getWidth() &&
+ size.getHeight() >= maxVideoSize.getHeight()) {
+ toBeRemoved.add(size);
+ }
+ }
+ jpegSizesList.removeAll(toBeRemoved);
+ }
+
+ if (hwLevel == FULL || hwLevel == LIMITED) {
+ if (!yuvSizesList.containsAll(jpegSizesList)) {
+ for (Size s : jpegSizesList) {
+ if (!yuvSizesList.contains(s)) {
+ fail("Size " + s + " not found in YUV format");
+ }
+ }
+ }
+ }
+
+ if (!privateSizesList.containsAll(yuvSizesList)) {
+ for (Size s : yuvSizesList) {
+ if (!privateSizesList.contains(s)) {
+ fail("Size " + s + " not found in PRIVATE format");
+ }
+ }
+ }
counter++;
}
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 b0c0fc0..76f1594 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
@@ -72,7 +72,9 @@
}
final int count = mEventGaps.size();
- int eventGapTolerance = (int) Math.min(EVENT_GAP_TOLERANCE_COUNT, 0.01 * mIndex);
+ // Ensure that eventGapTolerance is at least 1.
+ int eventGapTolerance = (int)Math.max(1, Math.min(EVENT_GAP_TOLERANCE_COUNT,
+ EVENT_GAP_TOLERANCE_PERCENT * mIndex));
stats.addValue(PASSED_KEY, count <= eventGapTolerance);
stats.addValue(SensorStats.EVENT_GAP_COUNT_KEY, count);
stats.addValue(SensorStats.EVENT_GAP_POSITIONS_KEY, getIndexArray(mEventGaps));
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java
index d69bf31..9e278bc 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java
@@ -84,8 +84,9 @@
sb.append(count).append(" events out of order: ");
for (int i = 0; i < Math.min(count, TRUNCATE_MESSAGE_LENGTH); i++) {
IndexedEventPair info = mOutOfOrderEvents.get(i);
- sb.append(String.format("position=%d, previous=%d, timestamp=%d; ", info.index,
- info.previousEvent.timestamp, info.event.timestamp));
+ sb.append(String.format("position=%d, previous_ts=%.2fms, current_ts=%.2fms",
+ info.index, nanosToMillis(info.previousEvent.timestamp),
+ nanosToMillis(info.event.timestamp)));
}
if (count > TRUNCATE_MESSAGE_LENGTH) {
sb.append(count - TRUNCATE_MESSAGE_LENGTH).append(" more");
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
index b9848fa..f1dc229 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
@@ -41,16 +41,6 @@
}
/**
- * Test that the verification passes when the timestamps are the same.
- */
- public void testSameTimestamp() {
- SensorStats stats = new SensorStats();
- EventOrderingVerification verification = getVerification(0, 0, 0, 0, 0);
- verification.verify(stats);
- verifyStats(stats, true, 0);
- }
-
- /**
* Test that the verification passes when the timestamps are increasing.
*/
public void testSequentialTimestamp() {
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 6f97439..15ff5c0 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
@@ -71,11 +71,10 @@
long fifoMaxEventCount = environment.getSensor().getFifoMaxEventCount();
int maximumExpectedSamplingPeriodUs = environment.getMaximumExpectedSamplingPeriodUs();
if (fifoMaxEventCount > 0 && maximumExpectedSamplingPeriodUs != Integer.MAX_VALUE) {
- long fifoBasedReportLatencyUs =
- fifoMaxEventCount * maximumExpectedSamplingPeriodUs;
- reportLatencyUs = Math.min(reportLatencyUs, fifoBasedReportLatencyUs) +
- (long)(2.5 * maximumExpectedSamplingPeriodUs);
- // of each event will be equal to the time it takes to fill up the FIFO.
+ long fifoBasedReportLatencyUs = fifoMaxEventCount * maximumExpectedSamplingPeriodUs;
+ // If the device goes into suspend mode and the sensor is a non wake-up sensor, the
+ // FIFO will keep overwriting itself and the reportLatency will be equal to the time
+ // it takes to fill up the FIFO.
if (environment.isDeviceSuspendTest() && !environment.getSensor().isWakeUpSensor()) {
reportLatencyUs = fifoBasedReportLatencyUs;
} else {
@@ -86,7 +85,9 @@
reportLatencyUs = Math.min(reportLatencyUs, fifoBasedReportLatencyUs);
}
}
- long expectedSyncLatencyNs = TimeUnit.MICROSECONDS.toNanos(reportLatencyUs);
+ // Add an additional filter delay which is a function of the samplingPeriod.
+ long filterDelayUs = (long)(2.5 * maximumExpectedSamplingPeriodUs);
+ long expectedSyncLatencyNs = TimeUnit.MICROSECONDS.toNanos(reportLatencyUs + filterDelayUs);
return new EventTimestampSynchronizationVerification(DEFAULT_THRESHOLD_NS,
expectedSyncLatencyNs);
}
diff --git a/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java b/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java
new file mode 100644
index 0000000..95704b9
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.hardware.fingerprint.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.os.CancellationSignal;
+import android.test.AndroidTestCase;
+
+/**
+ * Basic test cases for FingerprintManager
+ */
+public class FingerprintManagerTest extends AndroidTestCase {
+ private enum AuthState {
+ AUTH_UNKNOWN, AUTH_ERROR, AUTH_FAILED, AUTH_SUCCEEDED
+ }
+ private AuthState mAuthState = AuthState.AUTH_UNKNOWN;
+ private FingerprintManager mFingerprintManager;
+
+ boolean mHasFingerprintManager;
+ private AuthenticationCallback mAuthCallback = new AuthenticationCallback() {
+
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ mAuthState = AuthState.AUTH_SUCCEEDED;
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(
+ android.hardware.fingerprint.FingerprintManager.AuthenticationResult result) {
+ mAuthState = AuthState.AUTH_SUCCEEDED;
+ }
+ };
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mAuthState = AuthState.AUTH_UNKNOWN;
+
+ PackageManager pm = getContext().getPackageManager();
+ if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ mHasFingerprintManager = true;
+ mFingerprintManager = (FingerprintManager)
+ getContext().getSystemService(Context.FINGERPRINT_SERVICE);
+ }
+ }
+
+ public void test_hasFingerprintHardware() {
+ if (!mHasFingerprintManager) {
+ return; // skip test if no fingerprint feature
+ }
+ assertTrue(mFingerprintManager.isHardwareDetected());
+ }
+
+ public void test_hasEnrolledFingerprints() {
+ if (!mHasFingerprintManager) {
+ return; // skip test if no fingerprint feature
+ }
+ boolean hasEnrolledFingerprints = mFingerprintManager.hasEnrolledFingerprints();
+ assertTrue(!hasEnrolledFingerprints);
+ }
+
+ public void test_authenticateNullCallback() {
+ if (!mHasFingerprintManager) {
+ return; // skip test if no fingerprint feature
+ }
+ boolean exceptionTaken = false;
+ CancellationSignal cancelAuth = new CancellationSignal();
+ try {
+ mFingerprintManager.authenticate(null, cancelAuth, 0, null, null);
+ } catch (IllegalArgumentException e) {
+ exceptionTaken = true;
+ } finally {
+ assertTrue(mAuthState == AuthState.AUTH_UNKNOWN);
+ assertTrue(exceptionTaken);
+ cancelAuth.cancel();
+ }
+ }
+
+ public void test_authenticate() {
+ if (!mHasFingerprintManager) {
+ return; // skip test if no fingerprint feature
+ }
+ boolean exceptionTaken = false;
+ CancellationSignal cancelAuth = new CancellationSignal();
+ try {
+ mFingerprintManager.authenticate(null, cancelAuth, 0, mAuthCallback, null);
+ } catch (IllegalArgumentException e) {
+ exceptionTaken = true;
+ } finally {
+ assertFalse(exceptionTaken);
+ // We should never get out of this state without user interaction
+ assertTrue(mAuthState == AuthState.AUTH_UNKNOWN);
+ cancelAuth.cancel();
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
deleted file mode 100644
index b16cadb..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.hardware.input.cts;
-
-import android.app.Activity;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class InputCtsActivity extends Activity {
- private InputCallback mInputCallback;
-
- @Override
- public boolean dispatchGenericMotionEvent(MotionEvent ev) {
- if (mInputCallback != null) {
- mInputCallback.onMotionEvent(ev);
- }
- return true;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (mInputCallback != null) {
- mInputCallback.onMotionEvent(ev);
- }
- return true;
- }
-
- @Override
- public boolean dispatchTrackballEvent(MotionEvent ev) {
- if (mInputCallback != null) {
- mInputCallback.onMotionEvent(ev);
- }
- return true;
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent ev) {
- if (mInputCallback != null) {
- mInputCallback.onKeyEvent(ev);
- }
- return true;
- }
-
- public void setInputCallback(InputCallback callback) {
- mInputCallback = callback;
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java
deleted file mode 100644
index 92fba12..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.hardware.input.cts.tests;
-
-import android.util.Log;
-import android.view.KeyEvent;
-
-import java.io.Writer;
-import java.util.List;
-
-import com.android.cts.hardware.R;
-
-public class GamepadTestCase extends InputTestCase {
- private static final String TAG = "GamepadTests";
-
- public void testButtonA() throws Exception {
- sendHidCommands(R.raw.gamepad_press_a);
- assertReceivedKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BUTTON_A);
- assertReceivedKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BUTTON_A);
- assertNoMoreEvents();
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
deleted file mode 100644
index fba5f51..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * 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.hardware.input.cts.tests;
-
-import android.app.UiAutomation;
-import android.hardware.input.cts.InputCtsActivity;
-import android.hardware.input.cts.InputCallback;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.List;
-import java.util.UUID;
-
-public class InputTestCase extends ActivityInstrumentationTestCase2<InputCtsActivity> {
- private static final String TAG = "InputTestCase";
- private static final String HID_EXECUTABLE = "hid";
- private static final int SHELL_UID = 2000;
- private static final String[] KEY_ACTIONS = {"DOWN", "UP", "MULTIPLE"};
-
- private File mFifo;
- private Writer mWriter;
-
- private BlockingQueue<KeyEvent> mKeys;
- private BlockingQueue<MotionEvent> mMotions;
- private InputListener mInputListener;
-
- public InputTestCase() {
- super(InputCtsActivity.class);
- mKeys = new LinkedBlockingQueue<KeyEvent>();
- mMotions = new LinkedBlockingQueue<MotionEvent>();
- mInputListener = new InputListener();
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mFifo = setupFifo();
- clearKeys();
- clearMotions();
- getActivity().setInputCallback(mInputListener);
- }
-
- @Override
- protected void tearDown() throws Exception {
- if (mFifo != null) {
- mFifo.delete();
- mFifo = null;
- }
- closeQuietly(mWriter);
- mWriter = null;
- super.tearDown();
- }
-
- /**
- * Sends the HID commands designated by the given resource id.
- * The commands must be in the format expected by the `hid` shell command.
- *
- * @param id The resource id from which to load the HID commands. This must be a "raw"
- * resource.
- */
- public void sendHidCommands(int id) {
- try {
- Writer w = getWriter();
- w.write(getEvents(id));
- w.flush();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Asserts that the application received a {@link android.view.KeyEvent} with the given action
- * and keycode.
- *
- * If other KeyEvents are received by the application prior to the expected KeyEvent, or no
- * KeyEvents are received within a reasonable amount of time, then this will throw an
- * AssertionFailedError.
- *
- * @param action The action to expect on the next KeyEvent
- * (e.g. {@link android.view.KeyEvent#ACTION_DOWN}).
- * @param keyCode The expected key code of the next KeyEvent.
- */
- public void assertReceivedKeyEvent(int action, int keyCode) {
- KeyEvent k = waitForKey();
- if (k == null) {
- fail("Timed out waiting for " + KeyEvent.keyCodeToString(keyCode)
- + " with action " + KEY_ACTIONS[action]);
- return;
- }
- assertEquals(action, k.getAction());
- assertEquals(keyCode, k.getKeyCode());
- }
-
- /**
- * Asserts that no more events have been received by the application.
- *
- * If any more events have been received by the application, this throws an
- * AssertionFailedError.
- */
- public void assertNoMoreEvents() {
- KeyEvent key;
- MotionEvent motion;
- if ((key = mKeys.poll()) != null) {
- fail("Extraneous key events generated: " + key);
- }
- if ((motion = mMotions.poll()) != null) {
- fail("Extraneous motion events generated: " + motion);
- }
- }
-
- private KeyEvent waitForKey() {
- try {
- return mKeys.poll(1, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- return null;
- }
- }
-
- private void clearKeys() {
- mKeys.clear();
- }
-
- private void clearMotions() {
- mMotions.clear();
- }
-
- private File setupFifo() throws ErrnoException {
- File dir = getActivity().getCacheDir();
- String filename = dir.getAbsolutePath() + File.separator + UUID.randomUUID().toString();
- Os.mkfifo(filename, 0666);
- File f = new File(filename);
- return f;
- }
-
- private Writer getWriter() throws IOException {
- if (mWriter == null) {
- UiAutomation ui = getInstrumentation().getUiAutomation();
- ui.executeShellCommand("hid " + mFifo.getAbsolutePath());
- mWriter = new FileWriter(mFifo);
- }
- return mWriter;
- }
-
- private String getEvents(int id) throws IOException {
- InputStream is =
- getInstrumentation().getTargetContext().getResources().openRawResource(id);
- return readFully(is);
- }
-
-
- private static void closeQuietly(AutoCloseable closeable) {
- if (closeable != null) {
- try {
- closeable.close();
- } catch (RuntimeException rethrown) {
- throw rethrown;
- } catch (Exception ignored) { }
- }
- }
-
- private static String readFully(InputStream is) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- int read = 0;
- byte[] buffer = new byte[1024];
- while ((read = is.read(buffer)) >= 0) {
- baos.write(buffer, 0, read);
- }
- return baos.toString();
- }
-
- private class InputListener implements InputCallback {
- public void onKeyEvent(KeyEvent ev) {
- boolean done = false;
- do {
- try {
- mKeys.put(new KeyEvent(ev));
- done = true;
- } catch (InterruptedException ignore) { }
- } while (!done);
- }
-
- public void onMotionEvent(MotionEvent ev) {
- boolean done = false;
- do {
- try {
- mMotions.put(MotionEvent.obtain(ev));
- done = true;
- } catch (InterruptedException ignore) { }
- } while (!done);
- }
- }
-}
diff --git a/tests/tests/keystore/res/raw/ec_key3_secp224r1_cert.der b/tests/tests/keystore/res/raw/ec_key3_secp224r1_cert.der
new file mode 100644
index 0000000..454edb3
--- /dev/null
+++ b/tests/tests/keystore/res/raw/ec_key3_secp224r1_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/ec_key3_secp224r1_pkcs8.der b/tests/tests/keystore/res/raw/ec_key3_secp224r1_pkcs8.der
new file mode 100644
index 0000000..2b89ca3
--- /dev/null
+++ b/tests/tests/keystore/res/raw/ec_key3_secp224r1_pkcs8.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/ec_key4_secp256r1_cert.der b/tests/tests/keystore/res/raw/ec_key4_secp256r1_cert.der
new file mode 100644
index 0000000..1641a65
--- /dev/null
+++ b/tests/tests/keystore/res/raw/ec_key4_secp256r1_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/ec_key4_secp256r1_pkcs8.der b/tests/tests/keystore/res/raw/ec_key4_secp256r1_pkcs8.der
new file mode 100644
index 0000000..2631a6e
--- /dev/null
+++ b/tests/tests/keystore/res/raw/ec_key4_secp256r1_pkcs8.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/ec_key5_secp384r1_cert.der b/tests/tests/keystore/res/raw/ec_key5_secp384r1_cert.der
new file mode 100644
index 0000000..cf19be8
--- /dev/null
+++ b/tests/tests/keystore/res/raw/ec_key5_secp384r1_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/ec_key5_secp384r1_pkcs8.der b/tests/tests/keystore/res/raw/ec_key5_secp384r1_pkcs8.der
new file mode 100644
index 0000000..f864258
--- /dev/null
+++ b/tests/tests/keystore/res/raw/ec_key5_secp384r1_pkcs8.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/ec_key6_secp521r1_cert.der b/tests/tests/keystore/res/raw/ec_key6_secp521r1_cert.der
new file mode 100644
index 0000000..cc5d973
--- /dev/null
+++ b/tests/tests/keystore/res/raw/ec_key6_secp521r1_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/ec_key6_secp521r1_pkcs8.der b/tests/tests/keystore/res/raw/ec_key6_secp521r1_pkcs8.der
new file mode 100644
index 0000000..d21815f
--- /dev/null
+++ b/tests/tests/keystore/res/raw/ec_key6_secp521r1_pkcs8.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key3_1024_cert.der b/tests/tests/keystore/res/raw/rsa_key3_1024_cert.der
new file mode 100644
index 0000000..36f6820
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key3_1024_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key3_1024_pkcs8.der b/tests/tests/keystore/res/raw/rsa_key3_1024_pkcs8.der
new file mode 100644
index 0000000..3b70b0d
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key3_1024_pkcs8.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key4_4096_cert.der b/tests/tests/keystore/res/raw/rsa_key4_4096_cert.der
new file mode 100644
index 0000000..24d9a52
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key4_4096_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key4_4096_pkcs8.der b/tests/tests/keystore/res/raw/rsa_key4_4096_pkcs8.der
new file mode 100644
index 0000000..d3d08ca
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key4_4096_pkcs8.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key5_512_cert.der b/tests/tests/keystore/res/raw/rsa_key5_512_cert.der
new file mode 100644
index 0000000..56079dc
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key5_512_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key5_512_pkcs8.der b/tests/tests/keystore/res/raw/rsa_key5_512_pkcs8.der
new file mode 100644
index 0000000..8f723d3
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key5_512_pkcs8.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key6_768_cert.der b/tests/tests/keystore/res/raw/rsa_key6_768_cert.der
new file mode 100644
index 0000000..8afc59b
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key6_768_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key6_768_pkcs8.der b/tests/tests/keystore/res/raw/rsa_key6_768_pkcs8.der
new file mode 100644
index 0000000..7d25fe7
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key6_768_pkcs8.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key7_3072_cert.der b/tests/tests/keystore/res/raw/rsa_key7_3072_cert.der
new file mode 100644
index 0000000..414c35d
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key7_3072_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key7_3072_pksc8.der b/tests/tests/keystore/res/raw/rsa_key7_3072_pksc8.der
new file mode 100644
index 0000000..32788d2
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key7_3072_pksc8.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key8_2048_cert.der b/tests/tests/keystore/res/raw/rsa_key8_2048_cert.der
new file mode 100644
index 0000000..bbbd81b
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key8_2048_cert.der
Binary files differ
diff --git a/tests/tests/keystore/res/raw/rsa_key8_2048_pkcs8.der b/tests/tests/keystore/res/raw/rsa_key8_2048_pkcs8.der
new file mode 100644
index 0000000..94211ad
--- /dev/null
+++ b/tests/tests/keystore/res/raw/rsa_key8_2048_pkcs8.der
Binary files differ
diff --git a/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
index 3283a07..5fe3505 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
@@ -62,7 +62,6 @@
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
public class AndroidKeyStoreTest extends AndroidTestCase {
@@ -1888,7 +1887,7 @@
0x00, 0x05, (byte) 0xAA, (byte) 0x0A5, (byte) 0xFF, 0x55, 0x0A
};
- SecretKey expectedSecret = new SecretKeySpec(expectedKey, "AES");
+ SecretKey expectedSecret = new TransparentSecretKey(expectedKey, "AES");
byte[] wrappedExpected = c.wrap(expectedSecret);
@@ -2231,15 +2230,15 @@
long testStartTimeMillis = System.currentTimeMillis();
- SecretKey key1 =
- new SecretKeySpec(HexEncoding.decode("010203040506070809fafbfcfdfeffcc"), "AES");
+ SecretKey key1 = new TransparentSecretKey(
+ HexEncoding.decode("010203040506070809fafbfcfdfeffcc"), "AES");
String entryName1 = "test0";
- SecretKey key2 =
- new SecretKeySpec(HexEncoding.decode("808182838485868788897a7b7c7d7e7f"), "AES");
+ SecretKey key2 = new TransparentSecretKey(
+ HexEncoding.decode("808182838485868788897a7b7c7d7e7f"), "AES");
- SecretKey key3 =
- new SecretKeySpec(HexEncoding.decode("33333333333333333333777777777777"), "AES");
+ SecretKey key3 = new TransparentSecretKey(
+ HexEncoding.decode("33333333333333333333777777777777"), "AES");
mKeyStore.load(null);
int latestImportedEntryNumber = 0;
@@ -2322,14 +2321,14 @@
long testStartTimeMillis = System.currentTimeMillis();
- SecretKey key1 = new SecretKeySpec(
+ SecretKey key1 = new TransparentSecretKey(
HexEncoding.decode("010203040506070809fafbfcfdfeffcc"), "HmacSHA256");
String entryName1 = "test0";
- SecretKey key2 = new SecretKeySpec(
+ SecretKey key2 = new TransparentSecretKey(
HexEncoding.decode("808182838485868788897a7b7c7d7e7f"), "HmacSHA256");
- SecretKey key3 = new SecretKeySpec(
+ SecretKey key3 = new TransparentSecretKey(
HexEncoding.decode("33333333333333333333777777777777"), "HmacSHA256");
mKeyStore.load(null);
@@ -2395,4 +2394,183 @@
Log.i(TAG, "Deleted " + (latestImportedEntryNumber + 1) + " keys");
}
}
+
+ public void testKeyStore_OnlyOneDigestCanBeAuthorized_HMAC() throws Exception {
+ mKeyStore.load(null);
+
+ for (String algorithm : KeyGeneratorTest.EXPECTED_ALGORITHMS) {
+ if (!TestUtils.isHmacAlgorithm(algorithm)) {
+ continue;
+ }
+ try {
+ String digest = TestUtils.getHmacAlgorithmDigest(algorithm);
+ assertNotNull(digest);
+ SecretKey keyBeingImported = new TransparentSecretKey(new byte[16], algorithm);
+
+ KeyProtection.Builder goodSpec =
+ new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN);
+
+ // Digests authorization not specified in import parameters
+ assertFalse(goodSpec.build().isDigestsSpecified());
+ mKeyStore.setEntry(TEST_ALIAS_1,
+ new KeyStore.SecretKeyEntry(keyBeingImported),
+ goodSpec.build());
+ SecretKey key = (SecretKey) mKeyStore.getKey(TEST_ALIAS_1, null);
+ TestUtils.assertContentsInAnyOrder(
+ Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
+
+ // The same digest is specified in import parameters
+ mKeyStore.setEntry(TEST_ALIAS_1,
+ new KeyStore.SecretKeyEntry(keyBeingImported),
+ TestUtils.buildUpon(goodSpec).setDigests(digest).build());
+ key = (SecretKey) mKeyStore.getKey(TEST_ALIAS_1, null);
+ TestUtils.assertContentsInAnyOrder(
+ Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
+
+ // Empty set of digests specified in import parameters
+ try {
+ mKeyStore.setEntry(TEST_ALIAS_1,
+ new KeyStore.SecretKeyEntry(keyBeingImported),
+ TestUtils.buildUpon(goodSpec).setDigests().build());
+ fail();
+ } catch (KeyStoreException expected) {}
+
+ // A different digest specified in import parameters
+ String anotherDigest = "SHA-256".equalsIgnoreCase(digest) ? "SHA-384" : "SHA-256";
+ try {
+ mKeyStore.setEntry(TEST_ALIAS_1,
+ new KeyStore.SecretKeyEntry(keyBeingImported),
+ TestUtils.buildUpon(goodSpec).setDigests(anotherDigest).build());
+ fail();
+ } catch (KeyStoreException expected) {}
+ try {
+ mKeyStore.setEntry(TEST_ALIAS_1,
+ new KeyStore.SecretKeyEntry(keyBeingImported),
+ TestUtils.buildUpon(goodSpec)
+ .setDigests(digest, anotherDigest)
+ .build());
+ fail();
+ } catch (KeyStoreException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ public void testKeyStore_ImportSupportedSizes_AES() throws Exception {
+ mKeyStore.load(null);
+
+ KeyProtection params = new KeyProtection.Builder(
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .build();
+ String alias = "test1";
+ mKeyStore.deleteEntry(alias);
+ assertFalse(mKeyStore.containsAlias(alias));
+ for (int keySizeBytes = 0; keySizeBytes <= 512 / 8; keySizeBytes++) {
+ int keySizeBits = keySizeBytes * 8;
+ try {
+ KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(
+ new TransparentSecretKey(new byte[keySizeBytes], "AES"));
+ if (TestUtils.contains(KeyGeneratorTest.AES_SUPPORTED_KEY_SIZES, keySizeBits)) {
+ mKeyStore.setEntry(alias, entry, params);
+ SecretKey key = (SecretKey) mKeyStore.getKey(alias, null);
+ assertEquals("AES", key.getAlgorithm());
+ assertEquals(keySizeBits, TestUtils.getKeyInfo(key).getKeySize());
+ } else {
+ mKeyStore.deleteEntry(alias);
+ assertFalse(mKeyStore.containsAlias(alias));
+ try {
+ mKeyStore.setEntry(alias, entry, params);
+ fail();
+ } catch (KeyStoreException expected) {}
+ assertFalse(mKeyStore.containsAlias(alias));
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key size " + keySizeBits, e);
+ }
+ }
+ }
+
+ public void testKeyStore_ImportSupportedSizes_HMAC() throws Exception {
+ mKeyStore.load(null);
+
+ KeyProtection params = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build();
+ String alias = "test1";
+ mKeyStore.deleteEntry(alias);
+ assertFalse(mKeyStore.containsAlias(alias));
+ for (String algorithm : KeyGeneratorTest.EXPECTED_ALGORITHMS) {
+ if (!TestUtils.isHmacAlgorithm(algorithm)) {
+ continue;
+ }
+ for (int keySizeBytes = 0; keySizeBytes <= 1024 / 8; keySizeBytes++) {
+ try {
+ KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(
+ new TransparentSecretKey(new byte[keySizeBytes], algorithm));
+ if (keySizeBytes > 0) {
+ mKeyStore.setEntry(alias, entry, params);
+ SecretKey key = (SecretKey) mKeyStore.getKey(alias, null);
+ assertEquals(algorithm, key.getAlgorithm());
+ assertEquals(keySizeBytes * 8, TestUtils.getKeyInfo(key).getKeySize());
+ } else {
+ mKeyStore.deleteEntry(alias);
+ assertFalse(mKeyStore.containsAlias(alias));
+ try {
+ mKeyStore.setEntry(alias, entry, params);
+ fail();
+ } catch (KeyStoreException expected) {}
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + algorithm + " with key size " + (keySizeBytes * 8), e);
+ }
+ }
+ }
+ }
+
+ public void testKeyStore_ImportSupportedSizes_EC() throws Exception {
+ mKeyStore.load(null);
+ KeyProtection params =
+ TestUtils.getMinimalWorkingImportParametersForSigningingWith("SHA256withECDSA");
+ checkKeyPairImportSucceeds(
+ "secp224r1", R.raw.ec_key3_secp224r1_pkcs8, R.raw.ec_key3_secp224r1_cert, params);
+ checkKeyPairImportSucceeds(
+ "secp256r1", R.raw.ec_key4_secp256r1_pkcs8, R.raw.ec_key4_secp256r1_cert, params);
+ checkKeyPairImportSucceeds(
+ "secp384r1", R.raw.ec_key5_secp384r1_pkcs8, R.raw.ec_key5_secp384r1_cert, params);
+ checkKeyPairImportSucceeds(
+ "secp512r1", R.raw.ec_key6_secp521r1_pkcs8, R.raw.ec_key6_secp521r1_cert, params);
+ }
+
+ public void testKeyStore_ImportSupportedSizes_RSA() throws Exception {
+ mKeyStore.load(null);
+ KeyProtection params =
+ TestUtils.getMinimalWorkingImportParametersForSigningingWith("SHA256withRSA");
+ checkKeyPairImportSucceeds(
+ "512", R.raw.rsa_key5_512_pkcs8, R.raw.rsa_key5_512_cert, params);
+ checkKeyPairImportSucceeds(
+ "768", R.raw.rsa_key6_768_pkcs8, R.raw.rsa_key6_768_cert, params);
+ checkKeyPairImportSucceeds(
+ "1024", R.raw.rsa_key3_1024_pkcs8, R.raw.rsa_key3_1024_cert, params);
+ checkKeyPairImportSucceeds(
+ "2048", R.raw.rsa_key8_2048_pkcs8, R.raw.rsa_key8_2048_cert, params);
+ checkKeyPairImportSucceeds(
+ "3072", R.raw.rsa_key7_3072_pksc8, R.raw.rsa_key7_3072_cert, params);
+ checkKeyPairImportSucceeds(
+ "4096", R.raw.rsa_key4_4096_pkcs8, R.raw.rsa_key4_4096_cert, params);
+ }
+
+ private void checkKeyPairImportSucceeds(
+ String alias, int privateResId, int certResId, KeyProtection params) throws Exception {
+ try {
+ mKeyStore.deleteEntry(alias);
+ TestUtils.importIntoAndroidKeyStore(
+ alias, getContext(), privateResId, certResId, params);
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + alias, e);
+ } finally {
+ try {
+ mKeyStore.deleteEntry(alias);
+ } catch (Exception ignored) {}
+ }
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java b/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java
index f583c51..197adc2 100644
--- a/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java
+++ b/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java
@@ -212,7 +212,6 @@
// cause an error. Thus, there's no need to account for its ciphertext.
}
int actualOutputSize = mCipher.getOutputSize(input);
- System.out.println("*** buffered: " + buffered + ", input: " + input + ", min: " + minExpectedOutputSize + ", actual: " + actualOutputSize);
if (actualOutputSize < minExpectedOutputSize) {
fail("getOutputSize(" + input + ") underestimated output size when buffered == "
+ buffered + ". min expected: <"
@@ -290,7 +289,6 @@
minExpectedOutputSize = 0;
}
int actualOutputSize = mCipher.getOutputSize(input);
- System.out.println("*** buffered: " + buffered + ", input: " + input + ", min: " + minExpectedOutputSize + ", actual: " + actualOutputSize);
if (actualOutputSize < minExpectedOutputSize) {
fail("getOutputSize(" + input + ") underestimated output size when buffered == "
+ buffered + ". min expected: <"
diff --git a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
index 0300ec6..d9d4f43 100644
--- a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
@@ -26,16 +26,15 @@
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.Signature;
+import java.security.SignatureException;
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;
@@ -43,8 +42,9 @@
import java.util.Set;
import java.util.TreeMap;
+import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
+import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.OAEPParameterSpec;
@@ -212,9 +212,15 @@
private static final long DAY_IN_MILLIS = TestUtils.DAY_IN_MILLIS;
- private static final byte[] AES_KAT_KEY_BYTES =
+ private static final byte[] AES128_KAT_KEY_BYTES =
HexEncoding.decode("7d9f11a0da111e9d8bdd14f04648ed91");
+ private static final byte[] AES192_KAT_KEY_BYTES =
+ HexEncoding.decode("69ef2c44a48d3dc4d5744a281f7ebb5ca976c2202f91e10c");
+
+ private static final byte[] AES256_KAT_KEY_BYTES =
+ HexEncoding.decode("cf601cc10aaf434d1f01747136aff222af7fb426d101901712214c3fea18125f");
+
private static final KeyProtection.Builder GOOD_IMPORT_PARAMS_BUILDER =
new KeyProtection.Builder(
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
@@ -252,43 +258,43 @@
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 {
+ ImportedKey key = importDefaultKatKey(
+ algorithm,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false);
+
// 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));
+ cipher.init(Cipher.ENCRYPT_MODE, key.getKeystoreBackedEncryptionKey());
AlgorithmParameters params = cipher.getParameters();
// Test DECRYPT_MODE
- Key key = getDecryptionKey(algorithm, secretKeys, keyPairs);
cipher = Cipher.getInstance(algorithm);
- cipher.init(Cipher.DECRYPT_MODE, key, params);
+ Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
assertSame(provider, cipher.getProvider());
// Test UNWRAP_MODE
cipher = Cipher.getInstance(algorithm);
if (params != null) {
- cipher.init(Cipher.UNWRAP_MODE, key, params);
+ cipher.init(Cipher.UNWRAP_MODE, decryptionKey, params);
} else {
- cipher.init(Cipher.UNWRAP_MODE, key);
+ cipher.init(Cipher.UNWRAP_MODE, decryptionKey);
}
assertSame(provider, cipher.getProvider());
} catch (Throwable e) {
- throw new RuntimeException(algorithm + " failed", e);
+ throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testAndroidKeyStorePublicKeysAcceptedByHighestPriorityProviderWhenEncrypting()
throws Exception {
- Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
-
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
assertNotNull(provider);
for (String algorithm : EXPECTED_ALGORITHMS) {
@@ -296,7 +302,10 @@
continue;
}
try {
- Key key = getEncryptionKey(algorithm, Collections.<SecretKey>emptyList(), keyPairs);
+ Key key = importDefaultKatKey(
+ algorithm,
+ KeyProperties.PURPOSE_ENCRYPT,
+ false).getKeystoreBackedEncryptionKey();
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key);
@@ -304,192 +313,457 @@
cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.WRAP_MODE, key);
} catch (Throwable e) {
- throw new RuntimeException(algorithm + " failed", e);
+ throw new RuntimeException("Failed for" + algorithm, e);
+ }
+ }
+ }
+
+ public void testEmptyPlaintextEncryptsAndDecrypts()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ final byte[] originalPlaintext = EmptyArray.BYTE;
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ for (ImportedKey key : importKatKeys(
+ algorithm,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false)) {
+ try {
+ Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+ byte[] plaintext = truncatePlaintextIfNecessary(
+ algorithm, encryptionKey, originalPlaintext);
+ if (plaintext == null) {
+ // Key is too short to encrypt anything using this transformation
+ continue;
+ }
+ Cipher cipher = Cipher.getInstance(algorithm, provider);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ AlgorithmParameters params = cipher.getParameters();
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ byte[] expectedPlaintext = 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 = (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+ expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+ expectedPlaintext, modulusLengthBytes);
+ }
+
+ cipher = Cipher.getInstance(algorithm, provider);
+ Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+ byte[] actualPlaintext = cipher.doFinal(ciphertext);
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + algorithm + " with key " + key.getAlias(),
+ e);
+ }
}
}
}
public void testCiphertextGeneratedByAndroidKeyStoreDecryptsByAndroidKeyStore()
throws Exception {
- Collection<SecretKey> secretKeys = importDefaultKatSecretKeys();
- Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
assertNotNull(provider);
+ final byte[] originalPlaintext = "Very secret message goes here...".getBytes("US-ASCII");
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);
- }
+ for (ImportedKey key : importKatKeys(
+ algorithm,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false)) {
+ try {
+ Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+ byte[] plaintext = truncatePlaintextIfNecessary(
+ algorithm, encryptionKey, originalPlaintext);
+ if (plaintext == null) {
+ // Key is too short to encrypt anything using this transformation
+ continue;
+ }
+ Cipher cipher = Cipher.getInstance(algorithm, provider);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ AlgorithmParameters params = cipher.getParameters();
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ byte[] expectedPlaintext = 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 = (TestUtils.getKeySizeBits(encryptionKey) + 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);
+ cipher = Cipher.getInstance(algorithm, provider);
+ Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+ byte[] actualPlaintext = cipher.doFinal(ciphertext);
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + algorithm + " with key " + key.getAlias(),
+ 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);
+ Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(keystoreProvider);
+ byte[] originalPlaintext = "Very secret message goes here...".getBytes("UTF-8");
for (String algorithm : EXPECTED_ALGORITHMS) {
- try {
- Key encryptionKey = getEncryptionKey(algorithm, secretKeys, keyPairs);
- Cipher cipher;
+ for (ImportedKey key : importKatKeys(
+ algorithm,
+ KeyProperties.PURPOSE_DECRYPT,
+ false)) {
+ Provider encryptionProvider = null;
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();
+ Key encryptionKey = key.getOriginalEncryptionKey();
+ byte[] plaintext = truncatePlaintextIfNecessary(
+ algorithm, encryptionKey, originalPlaintext);
+ if (plaintext == null) {
+ // Key is too short to encrypt anything using this transformation
+ continue;
}
- }
- 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;
+ try {
+ cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ } catch (InvalidKeyException e) {
+ // No providers support encrypting using this algorithm and key.
+ continue;
+ }
+ encryptionProvider = cipher.getProvider();
+ if (keystoreProvider == encryptionProvider) {
+ // This is covered by another test.
+ continue;
+ }
+ AlgorithmParameters params = cipher.getParameters();
- // 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()));
- }
+ // 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();
+ }
+ }
- 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);
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ byte[] expectedPlaintext = 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 = (TestUtils.getKeySizeBits(encryptionKey) + 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, keystoreProvider);
+ Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+ byte[] actualPlaintext = cipher.doFinal(ciphertext);
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + algorithm + " with key " + key.getAlias()
+ + ", encryption provider: " + encryptionProvider,
+ 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);
+ Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(keystoreProvider);
+ byte[] originalPlaintext = "Very secret message goes here...".getBytes("UTF-8");
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);
+ for (ImportedKey key : importKatKeys(
+ algorithm,
+ KeyProperties.PURPOSE_ENCRYPT,
+ false)) {
+ Provider decryptionProvider = null;
try {
- cipher = Cipher.getInstance(algorithm);
- if (params != null) {
- cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
- } else {
- cipher.init(Cipher.DECRYPT_MODE, decryptionKey);
+ Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+ byte[] plaintext = truncatePlaintextIfNecessary(
+ algorithm, encryptionKey, originalPlaintext);
+ if (plaintext == null) {
+ // Key is too short to encrypt anything using this transformation
+ continue;
}
- } catch (InvalidKeyException e) {
- // No providers support decrypting using this algorithm and key.
- continue;
+ Cipher cipher = Cipher.getInstance(algorithm, keystoreProvider);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ AlgorithmParameters params = cipher.getParameters();
+
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ byte[] expectedPlaintext = 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 = (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+ expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+ expectedPlaintext, modulusLengthBytes);
+ }
+
+ Key decryptionKey = key.getOriginalDecryptionKey();
+ 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;
+ }
+ decryptionProvider = cipher.getProvider();
+ if (keystoreProvider == decryptionProvider) {
+ // This is covered by another test.
+ continue;
+ }
+ byte[] actualPlaintext = cipher.doFinal(ciphertext);
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + algorithm + " with key " + key.getAlias()
+ + ", decryption provider: " + decryptionProvider,
+ e);
}
- if (provider == cipher.getProvider()) {
- // This is covered by another test.
- continue;
+ }
+ }
+ }
+
+ public void testMaxSizedPlaintextSupported() throws Exception {
+ Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(keystoreProvider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ if (isSymmetric(algorithm)) {
+ // No input length restrictions (except multiple of block size for some
+ // transformations).
+ continue;
+ }
+ for (ImportedKey key : importKatKeys(
+ algorithm,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false)) {
+ int plaintextSizeBytes = -1;
+ Provider otherProvider = null;
+ try {
+ Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+ int maxSupportedPlaintextSizeBytes =
+ TestUtils.getMaxSupportedPlaintextInputSizeBytes(
+ algorithm, encryptionKey);
+ if (maxSupportedPlaintextSizeBytes < 0) {
+ // Key too short to encrypt anything using this transformation.
+ continue;
+ } else if (maxSupportedPlaintextSizeBytes == Integer.MAX_VALUE) {
+ // No input length restrictions.
+ continue;
+ }
+ byte[] plaintext = new byte[maxSupportedPlaintextSizeBytes];
+ Arrays.fill(plaintext, (byte) 0xff);
+ plaintextSizeBytes = plaintext.length;
+
+ // Encrypt plaintext using Android Keystore Cipher
+ Cipher cipher = Cipher.getInstance(algorithm, keystoreProvider);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ AlgorithmParameters params = cipher.getParameters();
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ byte[] expectedPlaintext = 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 = (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+ expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+ expectedPlaintext, modulusLengthBytes);
+ }
+
+ // Check that ciphertext decrypts using Android Keystore Cipher
+ cipher = Cipher.getInstance(algorithm, keystoreProvider);
+ Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+ byte[] actualPlaintext = cipher.doFinal(ciphertext);
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+
+ // Check that ciphertext decrypts using the highest-priority provider.
+ cipher = Cipher.getInstance(algorithm);
+ decryptionKey = key.getOriginalDecryptionKey();
+ try {
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+ } catch (InvalidKeyException e) {
+ // No other providers offer decryption using this transformation and key.
+ continue;
+ }
+ otherProvider = cipher.getProvider();
+ if (otherProvider == keystoreProvider) {
+ // This has already been tested above.
+ continue;
+ }
+ actualPlaintext = cipher.doFinal(ciphertext);
+ MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + algorithm + " with key " + key.getAlias()
+ + " and " + plaintextSizeBytes + " long plaintext"
+ + ", other provider: " + otherProvider,
+ e);
}
- byte[] actualPlaintext = cipher.doFinal(ciphertext);
- MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
- } catch (Throwable e) {
- throw new RuntimeException(algorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testLargerThanMaxSizedPlaintextRejected() throws Exception {
+ Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(keystoreProvider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ if (isSymmetric(algorithm)) {
+ // No input length restrictions (except multiple of block size for some
+ // transformations).
+ continue;
+ }
+ for (ImportedKey key : importKatKeys(
+ algorithm,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false)) {
+ int plaintextSizeBytes = -1;
+ Provider otherProvider = null;
+ try {
+ Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+ int maxSupportedPlaintextSizeBytes =
+ TestUtils.getMaxSupportedPlaintextInputSizeBytes(
+ algorithm, encryptionKey);
+ if (maxSupportedPlaintextSizeBytes < 0) {
+ // Key too short to encrypt anything using this transformation.
+ continue;
+ } else if (maxSupportedPlaintextSizeBytes == Integer.MAX_VALUE) {
+ // No input length restrictions.
+ continue;
+ }
+ // Create plaintext which is one byte longer than maximum supported one.
+ byte[] plaintext = new byte[maxSupportedPlaintextSizeBytes + 1];
+ Arrays.fill(plaintext, (byte) 0xff);
+ plaintextSizeBytes = plaintext.length;
+
+ // Encrypting this plaintext using Android Keystore Cipher should fail.
+ Cipher cipher = Cipher.getInstance(algorithm, keystoreProvider);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+ // This transformation is special: it's supposed to throw a
+ // BadPaddingException instead of an IllegalBlockSizeException.
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail("Unexpectedly produced ciphertext (" + ciphertext.length
+ + " bytes): " + HexEncoding.encode(ciphertext) + " for "
+ + plaintext.length + " byte long plaintext");
+ } catch (BadPaddingException expected) {}
+ } else {
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail("Unexpectedly produced ciphertext (" + ciphertext.length
+ + " bytes): " + HexEncoding.encode(ciphertext) + " for "
+ + plaintext.length + " byte long plaintext");
+ } catch (IllegalBlockSizeException expected) {}
+ }
+
+ // Encrypting this plaintext using the highest-priority implementation should
+ // fail.
+ cipher = Cipher.getInstance(algorithm);
+ encryptionKey = key.getOriginalEncryptionKey();
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+ } catch (InvalidKeyException e) {
+ // No other providers support this transformation with this key.
+ continue;
+ }
+ otherProvider = cipher.getProvider();
+ if (otherProvider == keystoreProvider) {
+ // This has already been tested above.
+ continue;
+ }
+ if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+ // This transformation is special: it's supposed to throw a
+ // BadPaddingException instead of an IllegalBlockSizeException.
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail(otherProvider.getName() + " unexpectedly produced ciphertext ("
+ + ciphertext.length + " bytes): "
+ + HexEncoding.encode(ciphertext) + " for "
+ + plaintext.length + " byte long plaintext");
+ // TODO: Remove this workaround once Conscrypt's RSA Cipher Bug 22567458
+ // is fixed. Conscrypt's Cipher.doFinal throws a SignatureException.
+ // This code is unreachable because of the fail() above. It's here only
+ // so that the compiler does not complain about us catching
+ // SignatureException.
+ Signature sig = Signature.getInstance("SHA256withRSA");
+ sig.sign();
+ } catch (BadPaddingException | SignatureException expected) {}
+ } else {
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail(otherProvider.getName() + " unexpectedly produced ciphertext ("
+ + ciphertext.length + " bytes): "
+ + HexEncoding.encode(ciphertext) + " for "
+ + plaintext.length + " byte long plaintext");
+ // TODO: Remove the catching of RuntimeException workaround once the
+ // corresponding Bug 22567463 in Conscrypt is fixed.
+ } catch (IllegalBlockSizeException | RuntimeException expected) {}
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + algorithm + " with key " + key.getAlias()
+ + " and " + plaintextSizeBytes + " byte long plaintext"
+ + ", other provider: " + otherProvider,
+ 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);
+ ImportedKey key = importDefaultKatKey(algorithm,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ true);
KatVector testVector = KAT_VECTORS.get(algorithm);
assertNotNull(testVector);
Cipher cipher = Cipher.getInstance(algorithm, provider);
- cipher.init(Cipher.DECRYPT_MODE, key, testVector.params);
+ Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, 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;
+ int modulusLengthBytes = (TestUtils.getKeySizeBits(decryptionKey) + 7) / 8;
expectedPlaintext = TestUtils.leftPadWithZeroBytes(
expectedPlaintext, modulusLengthBytes);
}
@@ -498,9 +772,9 @@
// 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);
+ Key encryptionKey = key.getKeystoreBackedEncryptionKey();
cipher = Cipher.getInstance(algorithm, provider);
- cipher.init(Cipher.ENCRYPT_MODE, key, testVector.params);
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, testVector.params);
byte[] actualCiphertext = cipher.doFinal(testVector.plaintext);
MoreAsserts.assertEquals(testVector.ciphertext, actualCiphertext);
}
@@ -917,31 +1191,156 @@
}
}
+ public void testEntropyConsumption() throws Exception {
+ // Assert that encryption consumes the correct amount of entropy from the provided
+ // SecureRandom and that decryption consumes no entropy.
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ CountingSecureRandom rng = new CountingSecureRandom();
+ for (String transformation : EXPECTED_ALGORITHMS) {
+ for (ImportedKey key : importKatKeys(
+ transformation,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ true)) {
+ try {
+ Cipher cipher = Cipher.getInstance(transformation, provider);
+ Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+ byte[] plaintext = truncatePlaintextIfNecessary(
+ transformation, encryptionKey, new byte[32]);
+ if (plaintext == null) {
+ // Key too short to encrypt anything using this transformation.
+ continue;
+ }
+ Arrays.fill(plaintext, (byte) 0x1);
+
+ // Cipher.init may only consume entropy for generating the IV.
+ rng.resetCounters();
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, rng);
+ int expectedEntropyBytesConsumedDuringInit;
+ String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+ if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+ String blockMode =
+ TestUtils.getCipherBlockMode(transformation).toUpperCase(Locale.US);
+ // Entropy should consumed for IV generation only.
+ switch (blockMode) {
+ case "ECB":
+ expectedEntropyBytesConsumedDuringInit = 0;
+ break;
+ case "CBC":
+ case "CTR":
+ expectedEntropyBytesConsumedDuringInit = 16;
+ break;
+ case "GCM":
+ expectedEntropyBytesConsumedDuringInit = 12;
+ break;
+ default:
+ throw new RuntimeException("Unsupported block mode " + blockMode);
+ }
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ expectedEntropyBytesConsumedDuringInit = 0;
+ } else {
+ throw new RuntimeException("Unsupported key algorithm: " + transformation);
+ }
+ assertEquals(expectedEntropyBytesConsumedDuringInit, rng.getOutputSizeBytes());
+ AlgorithmParameters params = cipher.getParameters();
+
+ // Cipher.update should not consume entropy.
+ rng.resetCounters();
+ byte[] ciphertext = cipher.update(plaintext);
+ assertEquals(0, rng.getOutputSizeBytes());
+
+ // Cipher.doFinal may consume entropy to pad the message (RSA only).
+ rng.resetCounters();
+ ciphertext = TestUtils.concat(ciphertext, cipher.doFinal());
+ int expectedEntropyBytesConsumedDuringDoFinal;
+ if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+ expectedEntropyBytesConsumedDuringDoFinal = 0;
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ // Entropy should not be consumed during Cipher.init.
+ String encryptionPadding =
+ TestUtils.getCipherEncryptionPadding(transformation);
+ if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(
+ encryptionPadding)) {
+ int digestOutputSizeBits =
+ TestUtils.getDigestOutputSizeBits(TestUtils.getCipherDigest(
+ transformation));
+ expectedEntropyBytesConsumedDuringDoFinal =
+ (digestOutputSizeBits + 7) / 8;
+ } else if (encryptionPadding.equalsIgnoreCase(
+ KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)) {
+ expectedEntropyBytesConsumedDuringDoFinal =
+ (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+ } else if (encryptionPadding.equalsIgnoreCase(
+ KeyProperties.ENCRYPTION_PADDING_NONE)) {
+ expectedEntropyBytesConsumedDuringDoFinal = 0;
+ } else {
+ throw new RuntimeException(
+ "Unexpected encryption padding: " + encryptionPadding);
+ }
+ } else {
+ throw new RuntimeException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ assertEquals(
+ expectedEntropyBytesConsumedDuringDoFinal, rng.getOutputSizeBytes());
+
+ // Assert that when initialization parameters are provided when encrypting, no
+ // entropy is consumed by Cipher.init. This is because Cipher.init should only
+ // use entropy for generating an IV which in this case no longer needs to be
+ // generated because it's specified in the parameters.
+ cipher = Cipher.getInstance(transformation, provider);
+ rng.resetCounters();
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, params, rng);
+ assertEquals(0, rng.getOutputSizeBytes());
+ Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+ rng.resetCounters();
+ cipher = Cipher.getInstance(transformation, provider);
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params, rng);
+ assertEquals(0, rng.getOutputSizeBytes());
+ rng.resetCounters();
+ cipher.update(ciphertext);
+ assertEquals(0, rng.getOutputSizeBytes());
+ rng.resetCounters();
+ cipher.doFinal();
+ assertEquals(0, rng.getOutputSizeBytes());
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + transformation + " with key " + key.getAlias(), e);
+ }
+ }
+ }
+ }
+
private AlgorithmParameterSpec getWorkingDecryptionParameterSpec(String transformation) {
- String transformationUpperCase = transformation.toUpperCase();
- if (transformationUpperCase.startsWith("RSA/")) {
+ String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+ if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
return null;
- } else if (transformationUpperCase.startsWith("AES/")) {
- if (transformationUpperCase.startsWith("AES/ECB")) {
+ } else if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+ String blockMode = TestUtils.getCipherBlockMode(transformation);
+ if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
return null;
- } else if ((transformationUpperCase.startsWith("AES/CBC"))
- || (transformationUpperCase.startsWith("AES/CTR"))) {
+ } else if ((KeyProperties.BLOCK_MODE_CBC.equalsIgnoreCase(blockMode))
+ || (KeyProperties.BLOCK_MODE_CTR.equalsIgnoreCase(blockMode))) {
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")) {
+ } else if (KeyProperties.BLOCK_MODE_GCM.equalsIgnoreCase(blockMode)) {
return new GCMParameterSpec(
128,
new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
+ } else {
+ throw new IllegalArgumentException("Unsupported block mode: " + blockMode);
}
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
-
- 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);
+ Key key =
+ importDefaultKatKey(transformation, importParams).getKeystoreBackedDecryptionKey();
AlgorithmParameterSpec params = getWorkingDecryptionParameterSpec(transformation);
cipher.init(Cipher.DECRYPT_MODE, key, params);
}
@@ -949,7 +1348,8 @@
private void assertInitDecryptThrowsInvalidKeyException(
String transformation, KeyProtection importParams) throws Exception {
Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
- Key key = importDefaultKatDecryptionKey(transformation, importParams);
+ Key key =
+ importDefaultKatKey(transformation, importParams).getKeystoreBackedDecryptionKey();
AlgorithmParameterSpec params = getWorkingDecryptionParameterSpec(transformation);
try {
cipher.init(Cipher.DECRYPT_MODE, key, params);
@@ -960,122 +1360,97 @@
private void assertInitEncryptSucceeds(String transformation, KeyProtection importParams)
throws Exception {
Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
- Key key = importDefaultKatEncryptionKey(transformation, importParams);
+ Key key =
+ importDefaultKatKey(transformation, importParams).getKeystoreBackedEncryptionKey();
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);
+ Key key =
+ importDefaultKatKey(transformation, importParams).getKeystoreBackedEncryptionKey();
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"),
+ private ImportedKey importDefaultKatKey(
+ String transformation, KeyProtection importParams)
+ throws Exception {
+ String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+ if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+ return TestUtils.importIntoAndroidKeyStore(
+ "testAES",
+ new SecretKeySpec(AES128_KAT_KEY_BYTES, "AES"),
+ importParams);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ return TestUtils.importIntoAndroidKeyStore(
+ "testRSA",
+ getContext(),
+ R.raw.rsa_key2_pkcs8,
+ R.raw.rsa_key2_cert,
importParams);
} else {
- throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
}
- 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);
+ private ImportedKey importDefaultKatKey(
+ String transformation, int purposes, boolean ivProvidedWhenEncrypting)
+ throws Exception {
+ KeyProtection importParams = TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ transformation, purposes, ivProvidedWhenEncrypting);
+ return importDefaultKatKey(transformation, importParams);
+ }
+
+ private Collection<ImportedKey> importKatKeys(
+ String transformation, int purposes, boolean ivProvidedWhenEncrypting)
+ throws Exception {
+ KeyProtection importParams = TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ transformation, purposes, ivProvidedWhenEncrypting);
+ String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+ if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+ return Arrays.asList(
+ TestUtils.importIntoAndroidKeyStore(
+ "testAES128",
+ new SecretKeySpec(AES128_KAT_KEY_BYTES, "AES"),
+ importParams),
+ TestUtils.importIntoAndroidKeyStore(
+ "testAES192",
+ new SecretKeySpec(AES192_KAT_KEY_BYTES, "AES"),
+ importParams),
+ TestUtils.importIntoAndroidKeyStore(
+ "testAES256",
+ new SecretKeySpec(AES256_KAT_KEY_BYTES, "AES"),
+ importParams)
+ );
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ return RSASignatureTest.importKatKeyPairs(getContext(), importParams);
} else {
- throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
}
- 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/");
+ return TestUtils.isCipherSymmetric(transformation);
+ }
+
+ private static byte[] truncatePlaintextIfNecessary(
+ String transformation, Key encryptionKey, byte[] plaintext) {
+ int maxSupportedPlaintextSizeBytes =
+ TestUtils.getMaxSupportedPlaintextInputSizeBytes(
+ transformation, encryptionKey);
+ if (plaintext.length <= maxSupportedPlaintextSizeBytes) {
+ // No need to truncate
+ return plaintext;
+ } else if (maxSupportedPlaintextSizeBytes < 0) {
+ // Key too short to encrypt anything at all using this transformation
+ return null;
+ } else {
+ // Truncate plaintext to exercise this transformation with this key
+ return Arrays.copyOf(plaintext, maxSupportedPlaintextSizeBytes);
+ }
}
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java
index 271b40b..ea3940d 100644
--- a/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java
@@ -16,38 +16,37 @@
package android.keystore.cts;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
+import android.content.Context;
+import android.security.keystore.KeyProtection;
+import android.test.AndroidTestCase;
-import junit.framework.TestCase;
+import com.android.cts.keystore.R;
import java.security.KeyPair;
-import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.Signature;
+import java.util.Arrays;
+import java.util.Collection;
-public class ECDSASignatureTest extends TestCase {
+public class ECDSASignatureTest extends AndroidTestCase {
public void testNONEwithECDSATruncatesInputToFieldSize() throws Exception {
- assertNONEwithECDSATruncatesInputToFieldSize(224);
- assertNONEwithECDSATruncatesInputToFieldSize(256);
- assertNONEwithECDSATruncatesInputToFieldSize(384);
- assertNONEwithECDSATruncatesInputToFieldSize(521);
+ for (ImportedKey key : importKatKeyPairs("NONEwithECDSA")) {
+ try {
+ assertNONEwithECDSATruncatesInputToFieldSize(key.getKeystoreBackedKeyPair());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + key.getAlias(), e);
+ }
+ }
}
- private void assertNONEwithECDSATruncatesInputToFieldSize(int keySizeBits) throws Exception {
+ private void assertNONEwithECDSATruncatesInputToFieldSize(KeyPair keyPair)
+ throws Exception {
+ int keySizeBits = TestUtils.getKeySizeBits(keyPair.getPublic());
byte[] message = new byte[(keySizeBits * 3) / 8];
for (int i = 0; i < message.length; i++) {
message[i] = (byte) (i + 1);
}
- KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
- generator.initialize(new KeyGenParameterSpec.Builder(
- "test1",
- KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_NONE)
- .setKeySize(keySizeBits)
- .build());
- KeyPair keyPair = generator.generateKeyPair();
Signature signature = Signature.getInstance("NONEwithECDSA");
signature.initSign(keyPair.getPrivate());
@@ -73,26 +72,23 @@
}
public void testNONEwithECDSASupportsMessagesShorterThanFieldSize() throws Exception {
- assertNONEwithECDSASupportsMessagesShorterThanFieldSize(224);
- assertNONEwithECDSASupportsMessagesShorterThanFieldSize(256);
- assertNONEwithECDSASupportsMessagesShorterThanFieldSize(384);
- assertNONEwithECDSASupportsMessagesShorterThanFieldSize(521);
+ for (ImportedKey key : importKatKeyPairs("NONEwithECDSA")) {
+ try {
+ assertNONEwithECDSASupportsMessagesShorterThanFieldSize(
+ key.getKeystoreBackedKeyPair());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + key.getAlias(), e);
+ }
+ }
}
- private void assertNONEwithECDSASupportsMessagesShorterThanFieldSize(
- int keySizeBits) throws Exception {
+ private void assertNONEwithECDSASupportsMessagesShorterThanFieldSize(KeyPair keyPair)
+ throws Exception {
+ int keySizeBits = TestUtils.getKeySizeBits(keyPair.getPublic());
byte[] message = new byte[(keySizeBits * 3 / 4) / 8];
for (int i = 0; i < message.length; i++) {
message[i] = (byte) (i + 1);
}
- KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
- generator.initialize(new KeyGenParameterSpec.Builder(
- "test1",
- KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_NONE)
- .setKeySize(keySizeBits)
- .build());
- KeyPair keyPair = generator.generateKeyPair();
Signature signature = Signature.getInstance("NONEwithECDSA");
signature.initSign(keyPair.getPrivate());
@@ -113,4 +109,25 @@
signature.update(fullLengthMessage);
assertTrue(signature.verify(sigBytes));
}
+
+ private Collection<ImportedKey> importKatKeyPairs(String signatureAlgorithm)
+ throws Exception {
+ KeyProtection params =
+ TestUtils.getMinimalWorkingImportParametersForSigningingWith(signatureAlgorithm);
+ return importKatKeyPairs(getContext(), params);
+ }
+
+ static Collection<ImportedKey> importKatKeyPairs(
+ Context context, KeyProtection importParams) throws Exception {
+ return Arrays.asList(new ImportedKey[] {
+ TestUtils.importIntoAndroidKeyStore("testECsecp224r1", context,
+ R.raw.ec_key3_secp224r1_pkcs8, R.raw.ec_key3_secp224r1_cert, importParams),
+ TestUtils.importIntoAndroidKeyStore("testECsecp256r1", context,
+ R.raw.ec_key4_secp256r1_pkcs8, R.raw.ec_key4_secp256r1_cert, importParams),
+ TestUtils.importIntoAndroidKeyStore("testECsecp384r1", context,
+ R.raw.ec_key5_secp384r1_pkcs8, R.raw.ec_key5_secp384r1_cert, importParams),
+ TestUtils.importIntoAndroidKeyStore("testECsecp521r1", context,
+ R.raw.ec_key6_secp521r1_pkcs8, R.raw.ec_key6_secp521r1_cert, importParams),
+ });
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java b/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java
index 90ac2c4..bf08b73 100644
--- a/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java
+++ b/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java
@@ -20,4 +20,5 @@
private EmptyArray() {}
public static final byte[] BYTE = new byte[0];
+ public static final String[] STRING = new String[0];
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java b/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java
new file mode 100644
index 0000000..b6c868f
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java
@@ -0,0 +1,150 @@
+/*
+ * 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 java.security.Key;
+import java.security.KeyPair;
+
+import javax.crypto.SecretKey;
+
+public class ImportedKey {
+ private final boolean mSymmetric;
+ private final String mAlias;
+ private final KeyPair mOriginalKeyPair;
+ private final KeyPair mKeystoreBackedKeyPair;
+ private final SecretKey mOriginalSecretKey;
+ private final SecretKey mKeystoreBackedSecretKey;
+
+ public ImportedKey(String alias, KeyPair original, KeyPair keystoreBacked) {
+ mAlias = alias;
+ mSymmetric = false;
+ mOriginalKeyPair = original;
+ mKeystoreBackedKeyPair = keystoreBacked;
+ mOriginalSecretKey = null;
+ mKeystoreBackedSecretKey = null;
+ }
+
+ public ImportedKey(String alias, SecretKey original, SecretKey keystoreBacked) {
+ mAlias = alias;
+ mSymmetric = true;
+ mOriginalKeyPair = null;
+ mKeystoreBackedKeyPair = null;
+ mOriginalSecretKey = original;
+ mKeystoreBackedSecretKey = keystoreBacked;
+ }
+
+ public String getAlias() {
+ return mAlias;
+ }
+
+ public Key getOriginalEncryptionKey() {
+ if (mSymmetric) {
+ return mOriginalSecretKey;
+ } else {
+ return mOriginalKeyPair.getPublic();
+ }
+ }
+
+ public Key getOriginalDecryptionKey() {
+ if (mSymmetric) {
+ return mOriginalSecretKey;
+ } else {
+ return mOriginalKeyPair.getPrivate();
+ }
+ }
+
+ public Key getOriginalSigningKey() {
+ if (mSymmetric) {
+ return mOriginalSecretKey;
+ } else {
+ return mOriginalKeyPair.getPrivate();
+ }
+ }
+
+ public Key getOriginalVerificationKey() {
+ if (mSymmetric) {
+ return mOriginalSecretKey;
+ } else {
+ return mOriginalKeyPair.getPublic();
+ }
+ }
+
+ public Key getKeystoreBackedEncryptionKey() {
+ if (mSymmetric) {
+ return mKeystoreBackedSecretKey;
+ } else {
+ return mKeystoreBackedKeyPair.getPublic();
+ }
+ }
+
+ public Key getKeystoreBackedDecryptionKey() {
+ if (mSymmetric) {
+ return mKeystoreBackedSecretKey;
+ } else {
+ return mKeystoreBackedKeyPair.getPrivate();
+ }
+ }
+
+ public Key getKeystoreBackedSigningKey() {
+ if (mSymmetric) {
+ return mKeystoreBackedSecretKey;
+ } else {
+ return mKeystoreBackedKeyPair.getPrivate();
+ }
+ }
+
+ public Key getKeystoreBackedVerificationKey() {
+ if (mSymmetric) {
+ return mKeystoreBackedSecretKey;
+ } else {
+ return mKeystoreBackedKeyPair.getPublic();
+ }
+ }
+
+
+ public KeyPair getOriginalKeyPair() {
+ checkIsKeyPair();
+ return mOriginalKeyPair;
+ }
+
+ public KeyPair getKeystoreBackedKeyPair() {
+ checkIsKeyPair();
+ return mKeystoreBackedKeyPair;
+ }
+
+ public SecretKey getOriginalSecretKey() {
+ checkIsSecretKey();
+ return mOriginalSecretKey;
+ }
+
+ public SecretKey getKeystoreBackedSecretKey() {
+ checkIsSecretKey();
+ return mKeystoreBackedSecretKey;
+ }
+
+ private void checkIsKeyPair() {
+ if (mSymmetric) {
+ throw new IllegalStateException("Not a KeyPair");
+ }
+ }
+
+ private void checkIsSecretKey() {
+ if (!mSymmetric) {
+ throw new IllegalStateException("Not a SecretKey");
+ }
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
index b51d300..6deaed4 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
@@ -46,7 +46,7 @@
public class KeyGeneratorTest extends TestCase {
private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME;
- private static final String[] EXPECTED_ALGORITHMS = {
+ static final String[] EXPECTED_ALGORITHMS = {
"AES",
"HmacSHA1",
"HmacSHA224",
@@ -66,7 +66,7 @@
DEFAULT_KEY_SIZES.put("HmacSHA512", 512);
}
- private static final int[] AES_SUPPORTED_KEY_SIZES = new int[] {128, 192, 256};
+ 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
@@ -244,7 +244,7 @@
assertEquals(0, rng.getOutputSizeBytes());
}
} catch (Throwable e) {
- throw new RuntimeException("Failed to key size " + i, e);
+ throw new RuntimeException("Failed for key size " + i, e);
}
}
}
@@ -291,6 +291,60 @@
}
}
+ public void testHmacKeyOnlyOneDigestCanBeAuthorized() throws Exception {
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ if (!TestUtils.isHmacAlgorithm(algorithm)) {
+ continue;
+ }
+
+ try {
+ String digest = TestUtils.getHmacAlgorithmDigest(algorithm);
+ assertNotNull(digest);
+
+ KeyGenParameterSpec.Builder goodSpec = new KeyGenParameterSpec.Builder(
+ "test1", KeyProperties.PURPOSE_SIGN);
+
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+
+ // Digests authorization not specified in algorithm parameters
+ assertFalse(goodSpec.build().isDigestsSpecified());
+ keyGenerator.init(goodSpec.build());
+ SecretKey key = keyGenerator.generateKey();
+ TestUtils.assertContentsInAnyOrder(
+ Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
+
+ // The same digest is specified in algorithm parameters
+ keyGenerator.init(TestUtils.buildUpon(goodSpec).setDigests(digest).build());
+ key = keyGenerator.generateKey();
+ TestUtils.assertContentsInAnyOrder(
+ Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
+
+ // No digests specified in algorithm parameters
+ try {
+ keyGenerator.init(TestUtils.buildUpon(goodSpec).setDigests().build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+
+ // A different digest specified in algorithm parameters
+ String anotherDigest = "SHA-256".equalsIgnoreCase(digest) ? "SHA-384" : "SHA-256";
+ try {
+ keyGenerator.init(TestUtils.buildUpon(goodSpec)
+ .setDigests(anotherDigest)
+ .build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ try {
+ keyGenerator.init(TestUtils.buildUpon(goodSpec)
+ .setDigests(digest, anotherDigest)
+ .build());
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
public void testInitWithUnknownBlockModeFails() {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
@@ -402,7 +456,7 @@
}
}
- public void testGenerateHonorsAuthorizations() throws Exception {
+ public void testGenerateHonorsRequestedAuthorizations() throws Exception {
Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS);
Date keyValidityForOriginationEnd =
new Date(System.currentTimeMillis() + TestUtils.DAY_IN_MILLIS);
@@ -418,13 +472,12 @@
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};
+ // HMAC key can only be authorized for one digest, the one implied by the key's
+ // JCA algorithm name.
+ digests = new String[] {TestUtils.getHmacAlgorithmDigest(algorithm)};
purposes = KeyProperties.PURPOSE_SIGN;
} else {
- digests = new String[] {KeyProperties.DIGEST_SHA384};
+ digests = new String[] {KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA1};
purposes = KeyProperties.PURPOSE_DECRYPT;
}
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
index 3d3c909..bc6ab9f 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
@@ -297,7 +297,7 @@
}
}
- public void testGenerateHonorsAuthorizations() throws Exception {
+ public void testGenerateHonorsRequestedAuthorizations() throws Exception {
Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS);
Date keyValidityForOriginationEnd =
new Date(System.currentTimeMillis() + TestUtils.DAY_IN_MILLIS);
diff --git a/tests/tests/keystore/src/android/keystore/cts/MacTest.java b/tests/tests/keystore/src/android/keystore/cts/MacTest.java
index 8dcf47e..c41dcda 100644
--- a/tests/tests/keystore/src/android/keystore/cts/MacTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/MacTest.java
@@ -26,6 +26,7 @@
import java.security.Provider.Service;
import java.security.Security;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
@@ -58,22 +59,103 @@
private static final byte[] SHORT_MSG_KAT_MESSAGE = HexEncoding.decode("a16037e3c901c9a1ab");
- private static final Map<String, byte[]> SHORT_MSG_KAT_MACS =
- new TreeMap<String, byte[]>(String.CASE_INSENSITIVE_ORDER);
+ private static final Map<String, Collection<KatVector>> SHORT_MSG_KAT_MACS =
+ new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
static {
// From RI
- SHORT_MSG_KAT_MACS.put("HmacSHA1", HexEncoding.decode(
- "47d5677267f0efe1f7416bf504b210765674ef50"));
- SHORT_MSG_KAT_MACS.put("HmacSHA224", HexEncoding.decode(
- "03a8bbcd05e7166bff5b0b2368709a0c61c0b9d94f1b4d65c0e04948"));
- SHORT_MSG_KAT_MACS.put("HmacSHA256", HexEncoding.decode(
- "17feed3de0b2d53b69b228c2d9d26e9d57b314c50d36662596777f49445df729"));
- SHORT_MSG_KAT_MACS.put("HmacSHA384", HexEncoding.decode(
- "26e034c6696d28722ffc446ff0994f835e616cc704517d283a29648aee1eca5569c792ada8a176cdc7"
- + "813a87437e4ea0"));
- SHORT_MSG_KAT_MACS.put("HmacSHA512", HexEncoding.decode(
- "15cff189d754d6612bd18d157c8e59ac2ecd9a4b97b2ef343b7778130f7741795d5d2dc2b7addb9a36"
- + "7677ad57833b42bfa0733f49b57afd6bc32cddc0dcebec"));
+ SHORT_MSG_KAT_MACS.put("HmacSHA1", Arrays.asList(
+ new KatVector(HexEncoding.decode("4818c5466a1d05bc8f6ad4"),
+ HexEncoding.decode("aa178e4d174dc90d1ac3d22386c32d1af82396e7")),
+ new KatVector(HexEncoding.decode("8d07f59f63fc493711a2217d8603cbc4d9874c58"),
+ HexEncoding.decode("002215de36f8836e966ff459af80c4b31fe0ca27")),
+ new KatVector(HexEncoding.decode("f85f1c94023968729e3b4a7f495bf31c283b710662"),
+ HexEncoding.decode("f518db3015203606ea15145ad16b3d981db32a77"))));
+ SHORT_MSG_KAT_MACS.put("HmacSHA224", Arrays.asList(
+ new KatVector(
+ HexEncoding.decode(
+ "ed959815cc03f01e19ce93c1a3318ae87a905b7c27351d571ee858"),
+ HexEncoding.decode(
+ "abece4641458461f8b6a46f7daa61fc6119344c5c4bb5e7967da0e3e")),
+ new KatVector(
+ HexEncoding.decode(
+ "9150e1ad6a9d12370a2423a5d95e9bc2dd73b4ee9bc96b03c9cc2fba"),
+ HexEncoding.decode(
+ "523aa06730d1abe7da2ba94a966cd20db56c771f1899e2850c31158c")),
+ new KatVector(
+ HexEncoding.decode(
+ "424d5e7375c2040543f76b97451b1c074ee93b81ad24cef23800ebfe529a74ee2b"
+ ),
+ HexEncoding.decode(
+ "7627a86d829f45e3295a25813219ed5291f80029b972192d32a845c3"))));
+ SHORT_MSG_KAT_MACS.put("HmacSHA256", Arrays.asList(
+ new KatVector(
+ HexEncoding.decode(
+ "74a7ec4c79419d76fa5d3bdbedc17e5bebf0ee011c609b9f4c9126091613"),
+ HexEncoding.decode(
+ "c17b62519155b0d7f005f465becf9a1610635ae46a2c4d2b255851f201689ba5"
+ )),
+ new KatVector(
+ HexEncoding.decode(
+ "42b44e6a1600bed10ca6c6dc24df2871790f948e73f9457fa4889c340cf69496"),
+ HexEncoding.decode(
+ "e9082a5db98c8086ad306ac23a1da9478eb5733757af6b1148d25fa1459290de")
+ ),
+ new KatVector(
+ HexEncoding.decode(
+ "20bfc407c62022fea95f046f8ade6ee4b232665a9e97f75d3e35f1a9447991651a"
+ ),
+ HexEncoding.decode(
+ "dbf10ca8c362aa665562065e76e42beb19444f61ab0828438714c82779b71a0d"
+ ))));
+ SHORT_MSG_KAT_MACS.put("HmacSHA384", Arrays.asList(
+ new KatVector(
+ HexEncoding.decode(
+ "7d277b2ec95fca68fe6ce0b665b28b48e128762714c66ca2c3405b432f6ab835e3"
+ ),
+ HexEncoding.decode(
+ "bf8555816d8fa058e1d0ed4be23abda522adfae629b6a8819dcc2416d00507782a"
+ + "c714fdbfc7a340da4e6cf646a619f1")),
+ new KatVector(
+ HexEncoding.decode(
+ "7b30abe948ceab9d94965f274fd2a21c966aa9bdf06476f94a0bcc6c20fd5d2bdc"
+ + "e21af7c6fdf6017bce342a701f55c3"),
+ HexEncoding.decode(
+ "0fe51798528407119a5884f65ad76409983e978e25ab8f82aa412c08e76c8065d2"
+ + "6dfdb1935de49036fb24262a532a29")),
+ new KatVector(
+ HexEncoding.decode(
+ "26f232a40ada35850a14edf8f23c9ca97898ac0fa18640e5a7835230fa68f630b1"
+ + "c4579bc059c318bb2c5da609db13f1567fa175a6439e1d729713d1fa"
+ + "1039a3db"),
+ HexEncoding.decode(
+ "a086c610a382c24bb05e8bdd12cffc01055ec98a8f071239360cf8135205ffee33"
+ + "2da2134bed2ec3efde8bf145d1d257"))));
+ SHORT_MSG_KAT_MACS.put("HmacSHA512", Arrays.asList(
+ new KatVector(
+ HexEncoding.decode(
+ "46fb12ef48d4e8162f0828a66c9f7124de"),
+ HexEncoding.decode(
+ "036320b51376f5840b03fdababac53c189d4d6b35f26f562a909f8ecac4a02c244"
+ + "bfddc8f4eb89e0d0909fd2d8a46b796175e619cff215a675ce309540"
+ + "42b1c9")),
+ new KatVector(
+ HexEncoding.decode(
+ "45b3f16b1a247dd76f72ab2d5019f87b94efeb9a2fc01da3ca347050302dbda9c1"
+ + "19cf991aaa30b747c808ec6bc19be7b5ae5e66176e38f222347a1659"
+ + "15d007"),
+ HexEncoding.decode(
+ "92817ce36858ccad20a903e15952565d241ebaa87e07655754470090f1c6b9252a"
+ + "cff9b873f36840fa8fdaaf91c6f9de3b82f46de0b1fdfa584eaf27de"
+ + "f52c65")),
+ new KatVector(
+ HexEncoding.decode(
+ "e91630c69c8c294755e27e5ccf01fe09e06de6c4e423c1c4ef0ac9b67f9af3cc6b"
+ + "bc6292d18cf6e76738888a948b49f9509b44eb3af6974ca7e61f5208"
+ + "b9f7dca3"),
+ HexEncoding.decode(
+ "6ff5616e9c38cef3d20076841c65b8747193eb8033ea61e8693715109e0e448966"
+ + "3d8abcb2b7cf0911e461202112819fb8650ba02bdce08aa0d24b3873"
+ + "30f18f"))));
}
private static final byte[] LONG_MSG_KAT_SEED = SHORT_MSG_KAT_MESSAGE;
@@ -125,11 +207,12 @@
}
public void testAndroidKeyStoreKeysHandledByAndroidKeyStoreProvider() throws Exception {
- SecretKey key = importDefaultKatKey();
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
assertNotNull(provider);
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
+ SecretKey key = importDefaultKatKey(algorithm);
+
// Generate a MAC
Mac mac = Mac.getInstance(algorithm);
mac.init(key);
@@ -140,13 +223,34 @@
}
}
- public void testMacGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore() throws Exception {
- SecretKey key = importDefaultKatKey();
-
+ public void testMacGeneratedForEmptyMessage() throws Exception {
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
assertNotNull(provider);
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
+ SecretKey key = importDefaultKatKey(algorithm);
+
+ // Generate a MAC
+ Mac mac = Mac.getInstance(algorithm, provider);
+ mac.init(key);
+ byte[] macBytes = mac.doFinal();
+ assertNotNull(macBytes);
+ if (macBytes.length == 0) {
+ fail("Empty MAC");
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(algorithm + " failed", e);
+ }
+ }
+ }
+
+ public void testMacGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore() throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String algorithm : EXPECTED_ALGORITHMS) {
+ try {
+ SecretKey key = importDefaultKatKey(algorithm);
+
// Generate a MAC
Mac mac = Mac.getInstance(algorithm, provider);
mac.init(key);
@@ -162,13 +266,13 @@
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 {
+ SecretKey key = getDefaultKatKey(algorithm);
+ SecretKey keystoreKey = importDefaultKatKey(algorithm);
+
// Generate a MAC
Mac mac = Mac.getInstance(algorithm, provider);
mac.init(keystoreKey);
@@ -184,14 +288,14 @@
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 {
+ SecretKey key = getDefaultKatKey(algorithm);
+ SecretKey keystoreKey = importDefaultKatKey(algorithm);
+
// Generate a MAC
Mac mac = Mac.getInstance(algorithm);
mac.init(key);
@@ -209,36 +313,47 @@
}
public void testSmallMsgKat() throws Exception {
- SecretKey key = importDefaultKatKey();
byte[] message = SHORT_MSG_KAT_MESSAGE;
for (String algorithm : EXPECTED_ALGORITHMS) {
- try {
- byte[] goodMacBytes = SHORT_MSG_KAT_MACS.get(algorithm);
- assertNotNull(goodMacBytes);
- assertMacVerifiesOneShot(algorithm, key, message, goodMacBytes);
- assertMacVerifiesFedOneByteAtATime(algorithm, key, message, goodMacBytes);
- assertMacVerifiesFedUsingFixedSizeChunks(algorithm, key, message, goodMacBytes, 3);
+ for (KatVector testVector : SHORT_MSG_KAT_MACS.get(algorithm)) {
+ byte[] keyBytes = testVector.key;
+ try {
+ SecretKey key = TestUtils.importIntoAndroidKeyStore(
+ "test",
+ new SecretKeySpec(keyBytes, algorithm),
+ getWorkingImportParams(algorithm)).getKeystoreBackedSecretKey();
- byte[] messageWithBitFlip = message.clone();
- messageWithBitFlip[messageWithBitFlip.length / 2] ^= 1;
- assertMacDoesNotVerifyOneShot(algorithm, key, messageWithBitFlip, goodMacBytes);
+ byte[] goodMacBytes = testVector.mac.clone();
+ assertNotNull(goodMacBytes);
+ assertMacVerifiesOneShot(algorithm, key, message, goodMacBytes);
+ assertMacVerifiesFedOneByteAtATime(algorithm, key, message, goodMacBytes);
+ assertMacVerifiesFedUsingFixedSizeChunks(
+ algorithm, key, message, goodMacBytes, 3);
- byte[] goodMacWithBitFlip = goodMacBytes.clone();
- goodMacWithBitFlip[goodMacWithBitFlip.length / 2] ^= 1;
- assertMacDoesNotVerifyOneShot(algorithm, key, message, goodMacWithBitFlip);
- } catch (Throwable e) {
- throw new RuntimeException("Failed for " + algorithm, e);
+ byte[] messageWithBitFlip = message.clone();
+ messageWithBitFlip[messageWithBitFlip.length / 2] ^= 1;
+ assertMacDoesNotVerifyOneShot(algorithm, key, messageWithBitFlip, goodMacBytes);
+
+ byte[] goodMacWithBitFlip = goodMacBytes.clone();
+ goodMacWithBitFlip[goodMacWithBitFlip.length / 2] ^= 1;
+ assertMacDoesNotVerifyOneShot(algorithm, key, message, goodMacWithBitFlip);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + algorithm + " with key " + HexEncoding.encode(keyBytes),
+ e);
+ }
}
}
}
public void testLargeMsgKat() throws Exception {
- SecretKey key = importDefaultKatKey();
byte[] message = TestUtils.generateLargeKatMsg(LONG_MSG_KAT_SEED, LONG_MSG_KAT_SIZE_BYTES);
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
+ SecretKey key = importDefaultKatKey(algorithm);
+
byte[] goodMacBytes = LONG_MSG_KAT_MACS.get(algorithm);
assertNotNull(goodMacBytes);
assertMacVerifiesOneShot(algorithm, key, message, goodMacBytes);
@@ -258,8 +373,8 @@
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParams(algorithm);
- assertInitSucceeds(algorithm, good.build());
+ KeyProtection good = getWorkingImportParams(algorithm);
+ assertInitSucceeds(algorithm, good);
assertInitThrowsInvalidKeyException(algorithm,
TestUtils.buildUpon(good, badPurposes).build());
} catch (Throwable e) {
@@ -271,12 +386,12 @@
public void testInitFailsWhenDigestNotAuthorized() throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParams(algorithm);
- assertInitSucceeds(algorithm, good.build());
+ KeyProtection good = getWorkingImportParams(algorithm);
+ assertInitSucceeds(algorithm, good);
String badKeyAlgorithm = ("HmacSHA256".equalsIgnoreCase(algorithm))
? "HmacSHA384" : "HmacSHA256";
- assertInitThrowsInvalidKeyException(algorithm, badKeyAlgorithm, good.build());
+ assertInitThrowsInvalidKeyException(algorithm, badKeyAlgorithm, good);
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
@@ -286,9 +401,10 @@
public void testInitFailsWhenKeyNotYetValid() throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParams(algorithm)
- .setKeyValidityStart(new Date(System.currentTimeMillis() - DAY_IN_MILLIS));
- assertInitSucceeds(algorithm, good.build());
+ KeyProtection good = TestUtils.buildUpon(getWorkingImportParams(algorithm))
+ .setKeyValidityStart(new Date(System.currentTimeMillis() - DAY_IN_MILLIS))
+ .build();
+ assertInitSucceeds(algorithm, good);
Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
assertInitThrowsInvalidKeyException(algorithm,
@@ -302,10 +418,11 @@
public void testInitFailsWhenKeyNoLongerValidForOrigination() throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParams(algorithm)
+ KeyProtection good = TestUtils.buildUpon(getWorkingImportParams(algorithm))
.setKeyValidityForOriginationEnd(
- new Date(System.currentTimeMillis() + DAY_IN_MILLIS));
- assertInitSucceeds(algorithm, good.build());
+ new Date(System.currentTimeMillis() + DAY_IN_MILLIS))
+ .build();
+ assertInitSucceeds(algorithm, good);
Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
assertInitThrowsInvalidKeyException(algorithm,
@@ -321,10 +438,11 @@
public void testInitIgnoresThatKeyNoLongerValidForConsumption() throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParams(algorithm)
+ KeyProtection good = TestUtils.buildUpon(getWorkingImportParams(algorithm))
.setKeyValidityForConsumptionEnd(
- new Date(System.currentTimeMillis() + DAY_IN_MILLIS));
- assertInitSucceeds(algorithm, good.build());
+ new Date(System.currentTimeMillis() + DAY_IN_MILLIS))
+ .build();
+ assertInitSucceeds(algorithm, good);
Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
assertInitSucceeds(algorithm,
@@ -452,33 +570,36 @@
} catch (InvalidKeyException expected) {}
}
- private SecretKey getDefaultKatKey() {
- return new SecretKeySpec(KAT_KEY, "HmacSHA1");
+ private SecretKey getDefaultKatKey(String keyAlgorithm) {
+ return new SecretKeySpec(KAT_KEY, keyAlgorithm);
}
- private SecretKey importDefaultKatKey() throws Exception {
- return importDefaultKatKey("HmacSHA1",
- new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setDigests(
- KeyProperties.DIGEST_SHA1,
- KeyProperties.DIGEST_SHA224,
- KeyProperties.DIGEST_SHA256,
- KeyProperties.DIGEST_SHA384,
- KeyProperties.DIGEST_SHA512)
- .build());
+ private SecretKey importDefaultKatKey(String keyAlgorithm) throws Exception {
+ return importDefaultKatKey(
+ keyAlgorithm,
+ new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build());
}
private SecretKey importDefaultKatKey(
String keyAlgorithm, KeyProtection keyProtection) throws Exception {
return TestUtils.importIntoAndroidKeyStore(
"test1",
- new SecretKeySpec(KAT_KEY, keyAlgorithm),
- keyProtection);
+ getDefaultKatKey(keyAlgorithm),
+ keyProtection).getKeystoreBackedSecretKey();
}
- private static KeyProtection.Builder getWorkingImportParams(
+ private static KeyProtection getWorkingImportParams(
@SuppressWarnings("unused") String algorithm) {
- return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN);
+ return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build();
+ }
+
+ private static class KatVector {
+ public byte[] key;
+ public byte[] mac;
+
+ public KatVector(byte[] key, byte[] mac) {
+ this.key = key;
+ this.mac = mac;
+ }
}
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java b/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java
new file mode 100644
index 0000000..3403df3
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java
@@ -0,0 +1,251 @@
+/*
+ * 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 java.math.BigInteger;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.interfaces.RSAKey;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+
+import android.security.keystore.KeyProperties;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+public class RSACipherTest extends AndroidTestCase {
+
+ private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
+
+ public void testNoPaddingEncryptionAndDecryptionSucceedsWithInputShorterThanModulus()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+ int modulusSizeBytes = (modulus.bitLength() + 7) / 8;
+
+ // 1-byte long input for which we know the output
+ byte[] input = new byte[] {1};
+ // Because of how RSA works, the output is 1 (left-padded with zero bytes).
+ byte[] expectedOutput = TestUtils.leftPadWithZeroBytes(input, modulusSizeBytes);
+
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ MoreAsserts.assertEquals(expectedOutput, cipher.doFinal(input));
+
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ MoreAsserts.assertEquals(expectedOutput, cipher.doFinal(input));
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingEncryptionSucceedsWithPlaintextOneSmallerThanModulus()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+ // Plaintext is one smaller than the modulus
+ byte[] plaintext =
+ TestUtils.getBigIntegerMagnitudeBytes(modulus.subtract(BigInteger.ONE));
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ MoreAsserts.assertEquals(plaintext, cipher.doFinal(ciphertext));
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingEncryptionFailsWithPlaintextEqualToModulus() throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT ,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+ // Plaintext is exactly the modulus
+ byte[] plaintext = TestUtils.getBigIntegerMagnitudeBytes(modulus);
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail("Unexpectedly produced ciphertext (" + ciphertext.length + " bytes): "
+ + HexEncoding.encode(ciphertext));
+ } catch (BadPaddingException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingEncryptionFailsWithPlaintextOneLargerThanModulus() throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+ // Plaintext is one larger than the modulus
+ byte[] plaintext =
+ TestUtils.getBigIntegerMagnitudeBytes(modulus.add(BigInteger.ONE));
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail("Unexpectedly produced ciphertext (" + ciphertext.length + " bytes): "
+ + HexEncoding.encode(ciphertext));
+ } catch (BadPaddingException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingEncryptionFailsWithPlaintextOneByteLongerThanModulus()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+ // Plaintext is one byte longer than the modulus. The message is filled with zeros
+ // (thus being 0 if treated as a BigInteger). This is on purpose, to check that the
+ // Cipher implementation rejects such long message without comparing it to the value
+ // of the modulus.
+ byte[] plaintext = new byte[((modulus.bitLength() + 7) / 8) + 1];
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail("Unexpectedly produced ciphertext (" + ciphertext.length + " bytes): "
+ + HexEncoding.encode(ciphertext));
+ } catch (IllegalBlockSizeException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingDecryptionFailsWithCiphertextOneByteLongerThanModulus()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_DECRYPT,
+ false))) {
+ try {
+ PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+ BigInteger modulus = ((RSAKey) privateKey).getModulus();
+
+ // Ciphertext is one byte longer than the modulus. The message is filled with zeros
+ // (thus being 0 if treated as a BigInteger). This is on purpose, to check that the
+ // Cipher implementation rejects such long message without comparing it to the value
+ // of the modulus.
+ byte[] ciphertext = new byte[((modulus.bitLength() + 7) / 8) + 1];
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ try {
+ byte[] plaintext = cipher.doFinal(ciphertext);
+ fail("Unexpectedly produced plaintext (" + ciphertext.length + " bytes): "
+ + HexEncoding.encode(plaintext));
+ } catch (IllegalBlockSizeException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingWithZeroMessage() throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+
+ byte[] plaintext = EmptyArray.BYTE;
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ // Ciphertext should be all zero bytes
+ byte[] expectedCiphertext = new byte[(TestUtils.getKeySizeBits(publicKey) + 7) / 8];
+ MoreAsserts.assertEquals(expectedCiphertext, ciphertext);
+
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ // Decrypted plaintext should also be all zero bytes
+ byte[] expectedPlaintext = new byte[expectedCiphertext.length];
+ MoreAsserts.assertEquals(expectedPlaintext, cipher.doFinal(ciphertext));
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java
new file mode 100644
index 0000000..9ae3043
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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 java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.RSAKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import com.android.cts.keystore.R;
+
+import android.content.Context;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.test.AndroidTestCase;
+
+public class RSASignatureTest extends AndroidTestCase {
+
+ private static final String EXPECTED_PROVIDER_NAME = SignatureTest.EXPECTED_PROVIDER_NAME;
+
+ private static final String[] SIGNATURE_ALGORITHMS;
+
+ static {
+ List<String> sigAlgs = new ArrayList<>();
+ for (String algorithm : SignatureTest.EXPECTED_SIGNATURE_ALGORITHMS) {
+ String keyAlgorithm = TestUtils.getSignatureAlgorithmKeyAlgorithm(algorithm);
+ if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ sigAlgs.add(algorithm);
+ }
+ }
+ SIGNATURE_ALGORITHMS = sigAlgs.toArray(new String[sigAlgs.size()]);
+ }
+
+ public void testMaxMessageSizeWhenNoDigestUsed() throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey keyPair : importKatKeyPairs("NONEwithRSA")) {
+ PublicKey publicKey = keyPair.getKeystoreBackedKeyPair().getPublic();
+ PrivateKey privateKey = keyPair.getKeystoreBackedKeyPair().getPrivate();
+ int modulusSizeBits = ((RSAKey) publicKey).getModulus().bitLength();
+ try {
+ int modulusSizeBytes = (modulusSizeBits + 7) / 8;
+ // PKCS#1 signature padding must be at least 11 bytes long (00 || 01 || PS || 00)
+ // where PS must be at least 8 bytes long).
+ int expectedMaxMessageSizeBytes = modulusSizeBytes - 11;
+ byte[] msg = new byte[expectedMaxMessageSizeBytes + 1];
+ Arrays.fill(msg, (byte) 0xf0);
+
+ // Assert that a message of expected maximum length is accepted
+ Signature signature = Signature.getInstance("NONEwithRSA", provider);
+ signature.initSign(privateKey);
+ signature.update(msg, 0, expectedMaxMessageSizeBytes);
+ byte[] sigBytes = signature.sign();
+
+ signature.initVerify(publicKey);
+ signature.update(msg, 0, expectedMaxMessageSizeBytes);
+ assertTrue(signature.verify(sigBytes));
+
+ // Assert that a message longer than expected maximum length is rejected
+ signature = Signature.getInstance(signature.getAlgorithm(), provider);
+ signature.initSign(privateKey);
+ try {
+ signature.update(msg, 0, expectedMaxMessageSizeBytes + 1);
+ signature.sign();
+ fail();
+ } catch (SignatureException expected) {
+ }
+
+ signature.initVerify(publicKey);
+ try {
+ signature.update(msg, 0, expectedMaxMessageSizeBytes + 1);
+ signature.verify(sigBytes);
+ fail();
+ } catch (SignatureException expected) {
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + modulusSizeBits + " bit key", e);
+ }
+ }
+ }
+
+ public void testSmallKeyRejected() throws Exception {
+ // Use a 512 bit key which should prevent the use of any digests larger than SHA-256
+ // because the padded form of the digested message will be larger than modulus size.
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (String algorithm : SIGNATURE_ALGORITHMS) {
+ try {
+ String digest = TestUtils.getSignatureAlgorithmDigest(algorithm);
+ if (KeyProperties.DIGEST_NONE.equalsIgnoreCase(digest)) {
+ // Ignore signature algorithms without digest -- this is tested in a separate
+ // test above.
+ continue;
+ }
+ int digestOutputSizeBits = TestUtils.getDigestOutputSizeBits(digest);
+ if (digestOutputSizeBits <= 256) {
+ // 256-bit and shorter digests are short enough to work with a 512 bit key.
+ continue;
+ }
+
+ KeyPair keyPair = TestUtils.importIntoAndroidKeyStore("test1",
+ getContext(),
+ R.raw.rsa_key5_512_pkcs8,
+ R.raw.rsa_key5_512_cert,
+ TestUtils.getMinimalWorkingImportParametersForSigningingWith(algorithm))
+ .getKeystoreBackedKeyPair();
+ assertEquals(512, ((RSAKey) keyPair.getPrivate()).getModulus().bitLength());
+ assertEquals(512, ((RSAKey) keyPair.getPublic()).getModulus().bitLength());
+
+ Signature signature = Signature.getInstance(algorithm, provider);
+ // Assert that either initSign or sign fails. We don't expect all keymaster
+ // implementations to fail early, during initSign.
+ try {
+ signature.initSign(keyPair.getPrivate());
+ signature.update("A message".getBytes("UTF-8"));
+ byte[] sigBytes = signature.sign();
+ fail("Unexpectedly generated a signature (" + sigBytes.length + " bytes): "
+ + HexEncoding.encode(sigBytes));
+ } catch (InvalidKeyException | SignatureException expected) {
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
+ private Collection<ImportedKey> importKatKeyPairs(String signatureAlgorithm)
+ throws Exception {
+ KeyProtection params =
+ TestUtils.getMinimalWorkingImportParametersForSigningingWith(signatureAlgorithm);
+ return importKatKeyPairs(getContext(), params);
+ }
+
+ static Collection<ImportedKey> importKatKeyPairs(
+ Context context, KeyProtection importParams) throws Exception {
+ return Arrays.asList(new ImportedKey[] {
+ TestUtils.importIntoAndroidKeyStore("testRSA512", context,
+ R.raw.rsa_key5_512_pkcs8, R.raw.rsa_key5_512_cert, importParams),
+ TestUtils.importIntoAndroidKeyStore("testRSA768", context,
+ R.raw.rsa_key6_768_pkcs8, R.raw.rsa_key6_768_cert, importParams),
+ TestUtils.importIntoAndroidKeyStore("testRSA1024", context,
+ R.raw.rsa_key3_1024_pkcs8, R.raw.rsa_key3_1024_cert, importParams),
+ TestUtils.importIntoAndroidKeyStore("testRSA2024", context,
+ R.raw.rsa_key8_2048_pkcs8, R.raw.rsa_key8_2048_cert, importParams),
+ TestUtils.importIntoAndroidKeyStore("testRSA3072", context,
+ R.raw.rsa_key7_3072_pksc8, R.raw.rsa_key7_3072_cert, importParams),
+ TestUtils.importIntoAndroidKeyStore("testRSA4096", context,
+ R.raw.rsa_key4_4096_pkcs8, R.raw.rsa_key4_4096_cert, importParams),
+ });
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/SecretKeyFactoryTest.java b/tests/tests/keystore/src/android/keystore/cts/SecretKeyFactoryTest.java
index 2bb8ecd..17307f7 100644
--- a/tests/tests/keystore/src/android/keystore/cts/SecretKeyFactoryTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/SecretKeyFactoryTest.java
@@ -94,9 +94,7 @@
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};
+ digests = new String[] {digest};
purposes = KeyProperties.PURPOSE_SIGN;
} else {
digests = new String[] {KeyProperties.DIGEST_SHA384};
diff --git a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
index ff6985a..ee6472f 100644
--- a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
@@ -36,6 +36,7 @@
import java.util.Set;
import java.util.TreeMap;
+import android.content.Context;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.test.AndroidTestCase;
@@ -49,7 +50,7 @@
static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
- private static final String[] EXPECTED_SIGNATURE_ALGORITHMS = {
+ static final String[] EXPECTED_SIGNATURE_ALGORITHMS = {
"NONEwithRSA",
"MD5withRSA",
"SHA1withRSA",
@@ -362,13 +363,11 @@
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);
+ KeyPair keyPair = importDefaultKatKeyPair(sigAlgorithm).getKeystoreBackedKeyPair();
Signature signature = Signature.getInstance(sigAlgorithm);
signature.initSign(keyPair.getPrivate());
assertSame(provider, signature.getProvider());
@@ -380,13 +379,11 @@
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);
+ KeyPair keyPair = importDefaultKatKeyPair(sigAlgorithm).getKeystoreBackedKeyPair();
Signature signature = Signature.getInstance(sigAlgorithm);
signature.initVerify(keyPair.getPublic());
} catch (Throwable e) {
@@ -395,114 +392,233 @@
}
}
- public void testSignatureGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore()
+ public void testValidSignatureGeneratedForEmptyMessage()
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);
+ for (ImportedKey key : importKatKeyPairsForSigning(getContext(), sigAlgorithm)) {
+ if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+ sigAlgorithm, key.getOriginalSigningKey())) {
+ continue;
+ }
+ try {
+ KeyPair keyPair = key.getKeystoreBackedKeyPair();
- // Generate a signature
- Signature signature = Signature.getInstance(sigAlgorithm, provider);
- signature.initSign(keyPair.getPrivate());
- byte[] message = "This is a test".getBytes("UTF-8");
- signature.update(message);
- byte[] sigBytes = signature.sign();
+ // Generate a signature
+ Signature signature = Signature.getInstance(sigAlgorithm, provider);
+ signature.initSign(keyPair.getPrivate());
+ byte[] sigBytes = signature.sign();
- // Assert that it verifies using our own Provider
- assertSignatureVerifiesOneShot(
- sigAlgorithm, provider, keyPair.getPublic(), message, sigBytes);
- } catch (Throwable e) {
- throw new RuntimeException(sigAlgorithm + " failed", e);
+ // Assert that it verifies using our own Provider
+ signature.initVerify(keyPair.getPublic());
+ assertTrue(signature.verify(sigBytes));
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + sigAlgorithm + " with key " + key.getAlias(), e);
+ }
+ }
+ }
+ }
+
+ public void testEmptySignatureDoesNotVerify()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ for (ImportedKey key : importKatKeyPairsForSigning(getContext(), sigAlgorithm)) {
+ if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+ sigAlgorithm, key.getOriginalSigningKey())) {
+ continue;
+ }
+ try {
+ KeyPair keyPair = key.getKeystoreBackedKeyPair();
+ Signature signature = Signature.getInstance(sigAlgorithm, provider);
+ signature.initVerify(keyPair.getPublic());
+ assertFalse(signature.verify(EmptyArray.BYTE));
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + sigAlgorithm + " with key " + key.getAlias(), e);
+ }
+ }
+ }
+ }
+
+ public void testSignatureGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ for (ImportedKey key : importKatKeyPairsForSigning(getContext(), sigAlgorithm)) {
+ if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+ sigAlgorithm, key.getOriginalSigningKey())) {
+ continue;
+ }
+ try {
+ KeyPair keyPair = key.getKeystoreBackedKeyPair();
+
+ // Generate a signature
+ Signature signature = Signature.getInstance(sigAlgorithm, provider);
+ signature.initSign(keyPair.getPrivate());
+ byte[] message = "This is a test".getBytes("UTF-8");
+ signature.update(message);
+ byte[] sigBytes = signature.sign();
+
+ // Assert that it verifies using our own Provider
+ assertSignatureVerifiesOneShot(
+ sigAlgorithm, provider, keyPair.getPublic(), message, sigBytes);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + sigAlgorithm + " with key " + key.getAlias(), 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.
+ for (ImportedKey key : importKatKeyPairsForSigning(getContext(), sigAlgorithm)) {
+ if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+ sigAlgorithm, key.getOriginalSigningKey())) {
continue;
}
- assertSignatureVerifiesOneShot(
- sigAlgorithm, verificationProvider, publicKey, message, sigBytes);
- } catch (Throwable e) {
- throw new RuntimeException(sigAlgorithm + " failed", e);
+ Provider verificationProvider = null;
+ try {
+ PrivateKey keystorePrivateKey = key.getKeystoreBackedKeyPair().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 = key.getOriginalKeyPair().getPublic();
+ 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(
+ "Failed for " + sigAlgorithm + " with key " + key.getAlias()
+ + ", verification provider: " + verificationProvider,
+ 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.
+ for (ImportedKey key : importKatKeyPairsForSigning(getContext(), sigAlgorithm)) {
+ if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+ sigAlgorithm, key.getOriginalSigningKey())) {
continue;
}
- byte[] message = "This is a test".getBytes("UTF-8");
- signature.update(message);
- byte[] sigBytes = signature.sign();
+ Provider signingProvider = null;
+ try {
+ PrivateKey privateKey = key.getOriginalKeyPair().getPrivate();
- // 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);
+ // 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 = key.getKeystoreBackedKeyPair().getPublic();
+ assertSignatureVerifiesOneShot(
+ sigAlgorithm, keystoreProvider, keystorePublicKey, message, sigBytes);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + sigAlgorithm + " with key " + key.getAlias()
+ + ", signing provider: " + signingProvider,
+ e);
+ }
+ }
+ }
+ }
+
+ // TODO: Re-enable this test once Signature.initSign passes SecureRandom to SPI (Bug 22485587).
+ public void DISABLED_testEntropyConsumption() throws Exception {
+ // Assert that signature generation consumes the correct amount of entropy from the provided
+ // SecureRandom. There is no need to check that Signature.verify does not consume entropy
+ // because Signature.initVerify does not take a SecureRandom.
+
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ CountingSecureRandom rng = new CountingSecureRandom();
+ for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ for (ImportedKey key : importKatKeyPairsForSigning(getContext(), sigAlgorithm)) {
+ if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+ sigAlgorithm, key.getOriginalSigningKey())) {
+ continue;
+ }
+ try {
+ KeyPair keyPair = key.getKeystoreBackedKeyPair();
+ PrivateKey privateKey = keyPair.getPrivate();
+ Signature signature = Signature.getInstance(sigAlgorithm, provider);
+
+ // Signature.initSign should not consume entropy.
+ rng.resetCounters();
+ signature.initSign(privateKey, rng);
+ assertEquals(0, rng.getOutputSizeBytes());
+
+ // Signature.update should not consume entropy.
+ byte[] message = "This is a test message".getBytes("UTF-8");
+ rng.resetCounters();
+ signature.update(message);
+ assertEquals(0, rng.getOutputSizeBytes());
+
+ // Signature.sign may consume entropy.
+ rng.resetCounters();
+ signature.sign();
+ int expectedEntropyBytesConsumed;
+ String algorithmUpperCase = sigAlgorithm.toUpperCase(Locale.US);
+ if (algorithmUpperCase.endsWith("WITHECDSA")) {
+ expectedEntropyBytesConsumed =
+ (TestUtils.getKeySizeBits(privateKey) + 7) / 8;
+ } else if (algorithmUpperCase.endsWith("WITHRSA")) {
+ expectedEntropyBytesConsumed = 0;
+ } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
+ expectedEntropyBytesConsumed = 20; // salt length
+ } else {
+ throw new RuntimeException("Unsupported algorithm: " + sigAlgorithm);
+ }
+ assertEquals(expectedEntropyBytesConsumed, rng.getOutputSizeBytes());
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + sigAlgorithm + " with key " + key.getAlias(), e);
+ }
}
}
}
public void testSmallMsgKat() throws Exception {
- Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
byte[] message = SHORT_MSG_KAT_MESSAGE;
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -511,7 +627,7 @@
try {
byte[] goodSigBytes = SHORT_MSG_KAT_SIGNATURES.get(algorithm);
assertNotNull(goodSigBytes);
- KeyPair keyPair = getKeyPairForSignatureAlgorithm(algorithm, keyPairs);
+ KeyPair keyPair = importDefaultKatKeyPair(algorithm).getKeystoreBackedKeyPair();
// Assert that AndroidKeyStore provider can verify the known good signature.
assertSignatureVerifiesOneShot(
algorithm, provider, keyPair.getPublic(), message, goodSigBytes);
@@ -601,23 +717,25 @@
}
public void testLongMsgKat() throws Exception {
- Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
byte[] message = TestUtils.generateLargeKatMsg(LONG_MSG_KAT_SEED, LONG_MSG_KAT_SIZE_BYTES);
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
assertNotNull(provider);
for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
- KeyPair keyPair = getKeyPairForSignatureAlgorithm(algorithm, keyPairs);
-
try {
- if (algorithm.toLowerCase(Locale.US).startsWith("nonewithrsa")) {
- // This algorithm cannot accept large messages
+ KeyPair keyPair = importDefaultKatKeyPair(algorithm).getKeystoreBackedKeyPair();
+ String digest = TestUtils.getSignatureAlgorithmDigest(algorithm);
+ String keyAlgorithm = TestUtils.getSignatureAlgorithmKeyAlgorithm(algorithm);
+ if ((KeyProperties.DIGEST_NONE.equalsIgnoreCase(digest))
+ && (!KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm))) {
+ // This algorithm does not accept large messages
Signature signature = Signature.getInstance(algorithm, provider);
signature.initSign(keyPair.getPrivate());
try {
signature.update(message);
- signature.sign();
- fail();
+ byte[] sigBytes = signature.sign();
+ fail("Unexpectedly generated signature (" + sigBytes.length + "): "
+ + HexEncoding.encode(sigBytes));
} catch (SignatureException expected) {}
// Bogus signature generated using SHA-256 digest -- shouldn't because the
@@ -650,8 +768,9 @@
signature.initSign(keyPair.getPrivate());
signature.update(message);
byte[] generatedSigBytes = signature.sign();
+ String paddingScheme = TestUtils.getSignatureAlgorithmPadding(algorithm);
boolean deterministicSignatureScheme =
- algorithm.toLowerCase().endsWith("withrsa");
+ KeyProperties.SIGNATURE_PADDING_RSA_PKCS1.equalsIgnoreCase(paddingScheme);
if (deterministicSignatureScheme) {
MoreAsserts.assertEquals(goodSigBytes, generatedSigBytes);
} else {
@@ -725,8 +844,8 @@
for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
- assertInitSignSucceeds(algorithm, good.build());
+ KeyProtection good = getMinimalWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good);
assertInitSignThrowsInvalidKeyException(algorithm,
TestUtils.buildUpon(good, badPurposes).build());
} catch (Throwable e) {
@@ -741,8 +860,8 @@
for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParamsForVerifying(algorithm);
- assertInitVerifySucceeds(algorithm, good.build());
+ KeyProtection good = getMinimalWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good);
assertInitVerifySucceeds(algorithm,
TestUtils.buildUpon(good, badPurposes).build());
} catch (Throwable e) {
@@ -754,12 +873,13 @@
public void testInitSignFailsWhenDigestNotAuthorized() throws Exception {
for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
- assertInitSignSucceeds(algorithm, good.build());
+ KeyProtection good = getMinimalWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good);
- String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ String digest = TestUtils.getSignatureAlgorithmDigest(algorithm);
String badDigest =
- (algorithmUpperCase.startsWith("SHA256")) ? "SHA-384" : "SHA-256";
+ (KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest))
+ ? KeyProperties.DIGEST_SHA384 : KeyProperties.DIGEST_SHA256;
assertInitSignThrowsInvalidKeyException(algorithm,
TestUtils.buildUpon(good).setDigests(badDigest).build());
} catch (Throwable e) {
@@ -771,12 +891,13 @@
public void testInitVerifyIgnoresThatDigestNotAuthorized() throws Exception {
for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParamsForVerifying(algorithm);
- assertInitVerifySucceeds(algorithm, good.build());
+ KeyProtection good = getMinimalWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good);
- String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ String digest = TestUtils.getSignatureAlgorithmDigest(algorithm);
String badDigest =
- (algorithmUpperCase.startsWith("SHA256")) ? "SHA-384" : "SHA-256";
+ (KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest))
+ ? KeyProperties.DIGEST_SHA384 : KeyProperties.DIGEST_SHA256;
assertInitVerifySucceeds(algorithm,
TestUtils.buildUpon(good).setDigests(badDigest).build());
} catch (Throwable e) {
@@ -788,22 +909,23 @@
public void testInitSignFailsWhenPaddingNotAuthorized() throws Exception {
for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
try {
- String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ String paddingScheme = TestUtils.getSignatureAlgorithmPadding(algorithm);
String badPaddingScheme;
- if (algorithmUpperCase.endsWith("WITHECDSA")) {
- // Test does not apply to ECDSA because ECDSA doesn't any signature padding
- // schemes.
+ if (paddingScheme == null) {
+ // No padding scheme used by this algorithm -- ignore.
continue;
- } else if (algorithmUpperCase.endsWith("WITHRSA")) {
+ } else if (KeyProperties.SIGNATURE_PADDING_RSA_PKCS1.equalsIgnoreCase(
+ paddingScheme)) {
badPaddingScheme = KeyProperties.SIGNATURE_PADDING_RSA_PSS;
- } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
+ } else if (KeyProperties.SIGNATURE_PADDING_RSA_PSS.equalsIgnoreCase(
+ paddingScheme)) {
badPaddingScheme = KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
} else {
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
}
- KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
- assertInitSignSucceeds(algorithm, good.build());
+ KeyProtection good = getMinimalWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good);
assertInitSignThrowsInvalidKeyException(algorithm,
TestUtils.buildUpon(good).setSignaturePaddings(badPaddingScheme).build());
} catch (Throwable e) {
@@ -815,22 +937,23 @@
public void testInitVerifyIgnoresThatPaddingNotAuthorized() throws Exception {
for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
try {
- String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ String paddingScheme = TestUtils.getSignatureAlgorithmPadding(algorithm);
String badPaddingScheme;
- if (algorithmUpperCase.endsWith("WITHECDSA")) {
- // Test does not apply to ECDSA because ECDSA doesn't any signature padding
- // schemes.
+ if (paddingScheme == null) {
+ // No padding scheme used by this algorithm -- ignore.
continue;
- } else if (algorithmUpperCase.endsWith("WITHRSA")) {
+ } else if (KeyProperties.SIGNATURE_PADDING_RSA_PKCS1.equalsIgnoreCase(
+ paddingScheme)) {
badPaddingScheme = KeyProperties.SIGNATURE_PADDING_RSA_PSS;
- } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
+ } else if (KeyProperties.SIGNATURE_PADDING_RSA_PSS.equalsIgnoreCase(
+ paddingScheme)) {
badPaddingScheme = KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
} else {
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
}
- KeyProtection.Builder good = getWorkingImportParamsForVerifying(algorithm);
- assertInitVerifySucceeds(algorithm, good.build());
+ KeyProtection good = getMinimalWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good);
assertInitVerifySucceeds(algorithm,
TestUtils.buildUpon(good).setSignaturePaddings(badPaddingScheme).build());
} catch (Throwable e) {
@@ -843,8 +966,8 @@
Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
try {
- KeyProtection.Builder good = getWorkingImportParamsForSigning(algorithm);
- assertInitSignSucceeds(algorithm, good.build());
+ KeyProtection good = getMinimalWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good);
assertInitSignThrowsInvalidKeyException(algorithm,
TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
} catch (Throwable e) {
@@ -857,8 +980,8 @@
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());
+ KeyProtection good = getMinimalWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good);
assertInitVerifySucceeds(algorithm,
TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
} catch (Throwable e) {
@@ -871,8 +994,8 @@
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());
+ KeyProtection good = getMinimalWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good);
assertInitSignThrowsInvalidKeyException(algorithm,
TestUtils.buildUpon(good)
.setKeyValidityForOriginationEnd(badEndDate)
@@ -887,8 +1010,8 @@
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());
+ KeyProtection good = getMinimalWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good);
assertInitVerifySucceeds(algorithm,
TestUtils.buildUpon(good)
.setKeyValidityForOriginationEnd(badEndDate)
@@ -903,8 +1026,8 @@
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());
+ KeyProtection good = getMinimalWorkingImportParamsForSigning(algorithm);
+ assertInitSignSucceeds(algorithm, good);
assertInitSignSucceeds(algorithm,
TestUtils.buildUpon(good)
.setKeyValidityForConsumptionEnd(badEndDate)
@@ -919,8 +1042,8 @@
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());
+ KeyProtection good = getMinimalWorkingImportParamsForVerifying(algorithm);
+ assertInitVerifySucceeds(algorithm, good);
assertInitVerifySucceeds(algorithm,
TestUtils.buildUpon(good)
.setKeyValidityForConsumptionEnd(badEndDate)
@@ -948,10 +1071,8 @@
int certResId,
KeyProtection keyProtection) throws Exception {
PublicKey publicKey = TestUtils.importIntoAndroidKeyStore(
- "test1",
- TestUtils.getRawResPrivateKey(getContext(), privateKeyResId),
- TestUtils.getRawResX509Certificate(getContext(), certResId),
- keyProtection)
+ "test1", getContext(), privateKeyResId, certResId, keyProtection)
+ .getKeystoreBackedKeyPair()
.getPublic();
Signature signature = Signature.getInstance(signatureAlgorithm, EXPECTED_PROVIDER_NAME);
signature.initVerify(publicKey);
@@ -974,10 +1095,8 @@
int certResId,
KeyProtection keyProtection) throws Exception {
PrivateKey privateKey = TestUtils.importIntoAndroidKeyStore(
- "test1",
- TestUtils.getRawResPrivateKey(getContext(), privateKeyResId),
- TestUtils.getRawResX509Certificate(getContext(), certResId),
- keyProtection)
+ "test1", getContext(), privateKeyResId, certResId, keyProtection)
+ .getKeystoreBackedKeyPair()
.getPrivate();
Signature signature = Signature.getInstance(signatureAlgorithm, EXPECTED_PROVIDER_NAME);
signature.initSign(privateKey);
@@ -1009,10 +1128,8 @@
int certResId,
KeyProtection keyProtection) throws Exception {
PrivateKey privateKey = TestUtils.importIntoAndroidKeyStore(
- "test1",
- TestUtils.getRawResPrivateKey(getContext(), privateKeyResId),
- TestUtils.getRawResX509Certificate(getContext(), certResId),
- keyProtection)
+ "test1", getContext(), privateKeyResId, certResId, keyProtection)
+ .getKeystoreBackedKeyPair()
.getPrivate();
Signature signature = Signature.getInstance(signatureAlgorithm, EXPECTED_PROVIDER_NAME);
try {
@@ -1022,72 +1139,39 @@
}
static int[] getDefaultKeyAndCertResIds(String signatureAlgorithm) {
- String sigAlgLowerCase = signatureAlgorithm.toLowerCase();
- if (sigAlgLowerCase.contains("ecdsa")) {
+ String keyAlgorithm = TestUtils.getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
return new int[] {R.raw.ec_key1_pkcs8, R.raw.ec_key1_cert};
- } else if (sigAlgLowerCase.contains("rsa")) {
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
return new int[] {R.raw.rsa_key1_pkcs8, R.raw.rsa_key1_cert};
} else {
- throw new IllegalArgumentException(
- "Unknown signature algorithm: " + signatureAlgorithm);
+ throw new IllegalArgumentException("Unknown key algorithm: " + keyAlgorithm);
}
}
- private static String getKeyAlgorithmForSignatureAlgorithm(String signatureAlgorithm) {
- String algLowerCase = signatureAlgorithm.toLowerCase();
- if (algLowerCase.contains("withecdsa")) {
- return KeyProperties.KEY_ALGORITHM_EC;
- } else if (algLowerCase.contains("withrsa")) {
- return KeyProperties.KEY_ALGORITHM_RSA;
+ private ImportedKey importDefaultKatKeyPair(String signatureAlgorithm) throws Exception {
+ String keyAlgorithm = TestUtils.getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+ KeyProtection importParams =
+ TestUtils.getMinimalWorkingImportParametersForSigningingWith(signatureAlgorithm);
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ return TestUtils.importIntoAndroidKeyStore(
+ "testEc",
+ getContext(),
+ R.raw.ec_key1_pkcs8,
+ R.raw.ec_key1_cert,
+ importParams);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ return TestUtils.importIntoAndroidKeyStore(
+ "testRsa",
+ getContext(),
+ R.raw.rsa_key1_pkcs8,
+ R.raw.rsa_key1_cert,
+ importParams);
} else {
- throw new IllegalArgumentException(
- "Unsupported signature algorithm: " + signatureAlgorithm);
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
}
- private static KeyPair getKeyPairForSignatureAlgorithm(String signatureAlgorithm,
- Iterable<KeyPair> keyPairs) {
- return TestUtils.getKeyPairForKeyAlgorithm(
- 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(
- "testRsa",
- TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key1_pkcs8),
- TestUtils.getRawResX509Certificate(getContext(), R.raw.rsa_key1_cert),
- new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_NONE)
- .setSignaturePaddings(
- KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
- KeyProperties.SIGNATURE_PADDING_RSA_PSS)
- .build()),
- TestUtils.importIntoAndroidKeyStore(
- "testEc",
- TestUtils.getRawResPrivateKey(getContext(), R.raw.ec_key1_pkcs8),
- TestUtils.getRawResX509Certificate(getContext(), R.raw.ec_key1_cert),
- new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN)
- .setDigests(KeyProperties.DIGEST_NONE)
- .build())
- );
- }
-
private void assertSignatureVerifiesOneShot(
String algorithm,
PublicKey publicKey,
@@ -1198,26 +1282,28 @@
}
}
- 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 getMinimalWorkingImportParamsForSigning(String algorithm) {
+ return TestUtils.getMinimalWorkingImportParametersForSigningingWith(algorithm);
}
- private static KeyProtection.Builder getWorkingImportParamsForVerifying(String algorithm) {
- return TestUtils.buildUpon(
- getWorkingImportParamsForSigning(algorithm),
- KeyProperties.PURPOSE_VERIFY);
+ private static KeyProtection getMinimalWorkingImportParamsForVerifying(
+ @SuppressWarnings("unused") String algorithm) {
+ // No need to authorize anything because verification does not use the private key.
+ // Operations using public keys do not need authorization.
+ return new KeyProtection.Builder(0).build();
+ }
+
+ static Collection<ImportedKey> importKatKeyPairsForSigning(
+ Context context, String signatureAlgorithm) throws Exception {
+ String keyAlgorithm = TestUtils.getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+ KeyProtection importParams =
+ TestUtils.getMinimalWorkingImportParametersForSigningingWith(signatureAlgorithm);
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ return ECDSASignatureTest.importKatKeyPairs(context, importParams);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ return RSASignatureTest.importKatKeyPairs(context, importParams);
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+ }
}
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
index 01ddf34..b1ba453 100644
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
@@ -27,10 +27,10 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
-import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
@@ -114,7 +114,7 @@
}
}
- private static int getKeySizeBits(Key key) {
+ static int getKeySizeBits(Key key) {
if (key instanceof ECKey) {
return ((ECKey) key).getParams().getCurve().getField().getFieldSize();
} else if (key instanceof RSAKey) {
@@ -374,18 +374,6 @@
return result;
}
- static Certificate generateSelfSignedCert(String keyAlgorithm) throws Exception {
- KeyPairGenerator generator =
- KeyPairGenerator.getInstance(keyAlgorithm, "AndroidKeyStore");
- generator.initialize(new KeyGenParameterSpec.Builder(
- "test1",
- KeyProperties.PURPOSE_SIGN)
- .build());
- KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- return keyStore.getCertificate("test1");
- }
-
static PrivateKey getRawResPrivateKey(Context context, int resId) throws Exception {
byte[] pkcs8EncodedForm;
try (InputStream in = context.getResources().openRawResource(resId)) {
@@ -426,7 +414,7 @@
(PrivateKey) keyStore.getKey(alias, null));
}
- static SecretKey importIntoAndroidKeyStore(
+ static ImportedKey importIntoAndroidKeyStore(
String alias,
SecretKey key,
KeyProtection keyProtection) throws Exception {
@@ -435,7 +423,35 @@
keyStore.setEntry(alias,
new KeyStore.SecretKeyEntry(key),
keyProtection);
- return (SecretKey) keyStore.getKey(alias, null);
+ return new ImportedKey(alias, key, (SecretKey) keyStore.getKey(alias, null));
+ }
+
+ static ImportedKey importIntoAndroidKeyStore(
+ String alias, Context context, int privateResId, int certResId, KeyProtection params)
+ throws Exception {
+ Certificate originalCert = TestUtils.getRawResX509Certificate(context, certResId);
+ PublicKey originalPublicKey = originalCert.getPublicKey();
+ PrivateKey originalPrivateKey = TestUtils.getRawResPrivateKey(context, privateResId);
+
+ // Check that the domain parameters match between the private key and the public key. This
+ // is to catch accidental errors where a test provides the wrong resource ID as one of the
+ // parameters.
+ if (!originalPublicKey.getAlgorithm().equalsIgnoreCase(originalPrivateKey.getAlgorithm())) {
+ throw new IllegalArgumentException("Key algorithm mismatch."
+ + " Public: " + originalPublicKey.getAlgorithm()
+ + ", private: " + originalPrivateKey.getAlgorithm());
+ }
+ assertKeyPairSelfConsistent(originalPublicKey, originalPrivateKey);
+
+ KeyPair keystoreBacked = TestUtils.importIntoAndroidKeyStore(
+ alias, originalPrivateKey, originalCert,
+ params);
+ assertKeyPairSelfConsistent(keystoreBacked);
+ assertKeyPairSelfConsistent(keystoreBacked.getPublic(), originalPrivateKey);
+ return new ImportedKey(
+ alias,
+ new KeyPair(originalCert.getPublicKey(), originalPrivateKey),
+ keystoreBacked);
}
static byte[] drain(InputStream in) throws IOException {
@@ -448,19 +464,26 @@
return result.toByteArray();
}
+ static KeyProtection.Builder buildUpon(KeyProtection params) {
+ return buildUponInternal(params, null);
+ }
+
+ static KeyProtection.Builder buildUpon(KeyProtection params, int newPurposes) {
+ return buildUponInternal(params, newPurposes);
+ }
+
static KeyProtection.Builder buildUpon(
KeyProtection.Builder builder) {
- return buildUponInternal(builder, null);
+ return buildUponInternal(builder.build(), null);
}
static KeyProtection.Builder buildUpon(
KeyProtection.Builder builder, int newPurposes) {
- return buildUponInternal(builder, newPurposes);
+ return buildUponInternal(builder.build(), newPurposes);
}
private static KeyProtection.Builder buildUponInternal(
- KeyProtection.Builder builder, Integer newPurposes) {
- KeyProtection spec = builder.build();
+ KeyProtection spec, Integer newPurposes) {
int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes;
KeyProtection.Builder result = new KeyProtection.Builder(purposes);
result.setBlockModes(spec.getBlockModes());
@@ -479,19 +502,26 @@
return result;
}
+ static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec) {
+ return buildUponInternal(spec, null);
+ }
+
+ static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec, int newPurposes) {
+ return buildUponInternal(spec, newPurposes);
+ }
+
static KeyGenParameterSpec.Builder buildUpon(
KeyGenParameterSpec.Builder builder) {
- return buildUponInternal(builder, null);
+ return buildUponInternal(builder.build(), null);
}
static KeyGenParameterSpec.Builder buildUpon(
KeyGenParameterSpec.Builder builder, int newPurposes) {
- return buildUponInternal(builder, newPurposes);
+ return buildUponInternal(builder.build(), newPurposes);
}
private static KeyGenParameterSpec.Builder buildUponInternal(
- KeyGenParameterSpec.Builder builder, Integer newPurposes) {
- KeyGenParameterSpec spec = builder.build();
+ KeyGenParameterSpec spec, Integer newPurposes) {
int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes;
KeyGenParameterSpec.Builder result =
new KeyGenParameterSpec.Builder(spec.getKeystoreAlias(), purposes);
@@ -587,4 +617,289 @@
}
return result;
}
+
+ static String getCipherKeyAlgorithm(String transformation) {
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ if (transformationUpperCase.startsWith("AES/")) {
+ return KeyProperties.KEY_ALGORITHM_AES;
+ } else if (transformationUpperCase.startsWith("RSA/")) {
+ return KeyProperties.KEY_ALGORITHM_RSA;
+ } else {
+ throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ }
+ }
+
+ static boolean isCipherSymmetric(String transformation) {
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ if (transformationUpperCase.startsWith("AES/")) {
+ return true;
+ } else if (transformationUpperCase.startsWith("RSA/")) {
+ return false;
+ } else {
+ throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ }
+ }
+
+ static String getCipherDigest(String transformation) {
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ if (transformationUpperCase.contains("/OAEP")) {
+ if (transformationUpperCase.endsWith("/OAEPPADDING")) {
+ return KeyProperties.DIGEST_SHA1;
+ } else if (transformationUpperCase.endsWith(
+ "/OAEPWITHSHA-1ANDMGF1PADDING")) {
+ return KeyProperties.DIGEST_SHA1;
+ } else if (transformationUpperCase.endsWith(
+ "/OAEPWITHSHA-224ANDMGF1PADDING")) {
+ return KeyProperties.DIGEST_SHA224;
+ } else if (transformationUpperCase.endsWith(
+ "/OAEPWITHSHA-256ANDMGF1PADDING")) {
+ return KeyProperties.DIGEST_SHA256;
+ } else if (transformationUpperCase.endsWith(
+ "/OAEPWITHSHA-384ANDMGF1PADDING")) {
+ return KeyProperties.DIGEST_SHA384;
+ } else if (transformationUpperCase.endsWith(
+ "/OAEPWITHSHA-512ANDMGF1PADDING")) {
+ return KeyProperties.DIGEST_SHA512;
+ } else {
+ throw new RuntimeException("Unsupported OAEP padding scheme: "
+ + transformation);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ static String getCipherEncryptionPadding(String transformation) {
+ String transformationUpperCase = transformation.toUpperCase(Locale.US);
+ if (transformationUpperCase.endsWith("/NOPADDING")) {
+ return KeyProperties.ENCRYPTION_PADDING_NONE;
+ } else if (transformationUpperCase.endsWith("/PKCS7PADDING")) {
+ return KeyProperties.ENCRYPTION_PADDING_PKCS7;
+ } else if (transformationUpperCase.endsWith("/PKCS1PADDING")) {
+ return KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
+ } else if (transformationUpperCase.split("/")[2].startsWith("OAEP")) {
+ return KeyProperties.ENCRYPTION_PADDING_RSA_OAEP;
+ } else {
+ throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+ }
+ }
+
+ static String getCipherBlockMode(String transformation) {
+ return transformation.split("/")[1].toUpperCase(Locale.US);
+ }
+
+ static String getSignatureAlgorithmDigest(String algorithm) {
+ String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ int withIndex = algorithmUpperCase.indexOf("WITH");
+ if (withIndex == -1) {
+ throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
+ }
+ String digest = algorithmUpperCase.substring(0, withIndex);
+ if (digest.startsWith("SHA")) {
+ digest = "SHA-" + digest.substring("SHA".length());
+ }
+ return digest;
+ }
+
+ static String getSignatureAlgorithmPadding(String algorithm) {
+ String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ if (algorithmUpperCase.endsWith("WITHECDSA")) {
+ return null;
+ } else if (algorithmUpperCase.endsWith("WITHRSA")) {
+ return KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
+ } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
+ return KeyProperties.SIGNATURE_PADDING_RSA_PSS;
+ } else {
+ throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
+ }
+ }
+
+ static String getSignatureAlgorithmKeyAlgorithm(String algorithm) {
+ String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+ if (algorithmUpperCase.endsWith("WITHECDSA")) {
+ return KeyProperties.KEY_ALGORITHM_EC;
+ } else if ((algorithmUpperCase.endsWith("WITHRSA"))
+ || (algorithmUpperCase.endsWith("WITHRSA/PSS"))) {
+ return KeyProperties.KEY_ALGORITHM_RSA;
+ } else {
+ throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
+ }
+ }
+
+ static boolean isKeyLongEnoughForSignatureAlgorithm(String algorithm, Key key) {
+ String keyAlgorithm = key.getAlgorithm();
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ // No length restrictions for ECDSA
+ return true;
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ // No length restrictions for RSA
+ String digest = getSignatureAlgorithmDigest(algorithm);
+ int digestOutputSizeBits = getDigestOutputSizeBits(digest);
+ if (digestOutputSizeBits == -1) {
+ // No digesting -- assume the key is long enough for the message
+ return true;
+ }
+ String paddingScheme = getSignatureAlgorithmPadding(algorithm);
+ int paddingOverheadBytes;
+ if (KeyProperties.SIGNATURE_PADDING_RSA_PKCS1.equalsIgnoreCase(paddingScheme)) {
+ paddingOverheadBytes = 30;
+ } else if (KeyProperties.SIGNATURE_PADDING_RSA_PSS.equalsIgnoreCase(paddingScheme)) {
+ paddingOverheadBytes = 22;
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported signature padding scheme: " + paddingScheme);
+ }
+ int minKeySizeBytes = paddingOverheadBytes + (digestOutputSizeBits + 7) / 8 + 1;
+ int keySizeBytes = ((RSAKey) key).getModulus().bitLength() / 8;
+ return keySizeBytes >= minKeySizeBytes;
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ }
+
+ static int getMaxSupportedPlaintextInputSizeBytes(String transformation, Key key) {
+ String keyAlgorithm = getCipherKeyAlgorithm(transformation);
+ if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+ return Integer.MAX_VALUE;
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ String encryptionPadding = getCipherEncryptionPadding(transformation);
+ int modulusSizeBytes = (getKeySizeBits(key) + 7) / 8;
+ if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding)) {
+ return modulusSizeBytes - 1;
+ } else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(
+ encryptionPadding)) {
+ return modulusSizeBytes - 11;
+ } else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(
+ encryptionPadding)) {
+ String digest = getCipherDigest(transformation);
+ int digestOutputSizeBytes = (getDigestOutputSizeBits(digest) + 7) / 8;
+ return modulusSizeBytes - 2 * digestOutputSizeBytes - 2;
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported encryption padding scheme: " + encryptionPadding);
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ }
+
+ static int getDigestOutputSizeBits(String digest) {
+ if (KeyProperties.DIGEST_NONE.equals(digest)) {
+ return -1;
+ } else if (KeyProperties.DIGEST_MD5.equals(digest)) {
+ return 128;
+ } else if (KeyProperties.DIGEST_SHA1.equals(digest)) {
+ return 160;
+ } else if (KeyProperties.DIGEST_SHA224.equals(digest)) {
+ return 224;
+ } else if (KeyProperties.DIGEST_SHA256.equals(digest)) {
+ return 256;
+ } else if (KeyProperties.DIGEST_SHA384.equals(digest)) {
+ return 384;
+ } else if (KeyProperties.DIGEST_SHA512.equals(digest)) {
+ return 512;
+ } else {
+ throw new IllegalArgumentException("Unsupported digest: " + digest);
+ }
+ }
+
+ static byte[] concat(byte[] arr1, byte[] arr2) {
+ return concat(arr1, 0, (arr1 != null) ? arr1.length : 0,
+ arr2, 0, (arr2 != null) ? arr2.length : 0);
+ }
+
+ static byte[] concat(byte[] arr1, int offset1, int len1,
+ byte[] arr2, int offset2, int len2) {
+ if (len1 == 0) {
+ return subarray(arr2, offset2, len2);
+ } else if (len2 == 0) {
+ return subarray(arr1, offset1, len1);
+ }
+ byte[] result = new byte[len1 + len2];
+ if (len1 > 0) {
+ System.arraycopy(arr1, offset1, result, 0, len1);
+ }
+ if (len2 > 0) {
+ System.arraycopy(arr2, offset2, result, len1, len2);
+ }
+ return result;
+ }
+
+ static byte[] subarray(byte[] arr, int offset, int len) {
+ if (len == 0) {
+ return EmptyArray.BYTE;
+ }
+ if ((offset == 0) && (arr.length == len)) {
+ return arr;
+ }
+ byte[] result = new byte[len];
+ System.arraycopy(arr, offset, result, 0, len);
+ return result;
+ }
+
+ static KeyProtection getMinimalWorkingImportParametersForSigningingWith(
+ String signatureAlgorithm) {
+ String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+ String digest = getSignatureAlgorithmDigest(signatureAlgorithm);
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+ .setDigests(digest)
+ .build();
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ String padding = getSignatureAlgorithmPadding(signatureAlgorithm);
+ return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+ .setDigests(digest)
+ .setSignaturePaddings(padding)
+ .build();
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported signature algorithm: " + signatureAlgorithm);
+ }
+ }
+
+ static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
+ String transformation, int purposes, boolean ivProvidedWhenEncrypting) {
+ String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+ if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+ String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
+ String blockMode = TestUtils.getCipherBlockMode(transformation);
+ boolean randomizedEncryptionRequired = true;
+ if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
+ randomizedEncryptionRequired = false;
+ } else if ((ivProvidedWhenEncrypting)
+ && ((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)) {
+ randomizedEncryptionRequired = false;
+ }
+ return new KeyProtection.Builder(
+ purposes)
+ .setBlockModes(blockMode)
+ .setEncryptionPaddings(encryptionPadding)
+ .setRandomizedEncryptionRequired(randomizedEncryptionRequired)
+ .build();
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ String digest = TestUtils.getCipherDigest(transformation);
+ String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
+ boolean randomizedEncryptionRequired =
+ !KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding);
+ return new KeyProtection.Builder(
+ purposes)
+ .setDigests((digest != null) ? new String[] {digest} : EmptyArray.STRING)
+ .setEncryptionPaddings(encryptionPadding)
+ .setRandomizedEncryptionRequired(randomizedEncryptionRequired)
+ .build();
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ }
+
+ static byte[] getBigIntegerMagnitudeBytes(BigInteger value) {
+ return removeLeadingZeroByteIfPresent(value.toByteArray());
+ }
+
+ private static byte[] removeLeadingZeroByteIfPresent(byte[] value) {
+ if ((value.length < 1) || (value[0] != 0)) {
+ return value;
+ }
+ return TestUtils.subarray(value, 1, value.length - 1);
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/TransparentSecretKey.java b/tests/tests/keystore/src/android/keystore/cts/TransparentSecretKey.java
new file mode 100644
index 0000000..6e74dc0
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/TransparentSecretKey.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;
+
+import javax.crypto.SecretKey;
+
+/**
+ * {@link SecretKey} which exposes its key material. The two reasons for the existence of this class
+ * are: (1) to help test that classes under test don't assume that all transparent secret keys are
+ * instances of {@link SecretKeySpec}, and (2) because {@code SecretKeySpec} rejects zero-length
+ * key material which is needed in some tests.
+ */
+public class TransparentSecretKey implements SecretKey {
+ private final String mAlgorithm;
+ private final byte[] mKeyMaterial;
+
+ public TransparentSecretKey(byte[] keyMaterial, String algorithm) {
+ mAlgorithm = algorithm;
+ mKeyMaterial = keyMaterial.clone();
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return mAlgorithm;
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return mKeyMaterial;
+ }
+
+ @Override
+ public String getFormat() {
+ return "RAW";
+ }
+}
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index 43e3e89..13daca6 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -41,7 +41,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctsmediautil ctsdeviceutil ctstestserver ctstestrunner
-LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni
+LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni libaudio_jni
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/media/libaudiojni/Android.mk b/tests/tests/media/libaudiojni/Android.mk
new file mode 100644
index 0000000..a6c1bfc
--- /dev/null
+++ b/tests/tests/media/libaudiojni/Android.mk
@@ -0,0 +1,39 @@
+# 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 := libaudio_jni
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+ appendix-b-1-1-buffer-queue.cpp \
+ appendix-b-1-2-recording.cpp \
+ audio-record-native.cpp \
+ audio-track-native.cpp \
+ sl-utils.cpp
+
+LOCAL_C_INCLUDES := \
+ $(JNI_H_INCLUDE) \
+ system/core/include
+
+LOCAL_C_INCLUDES += $(call include-path-for, libaudiojni) \
+ $(call include-path-for, wilhelm)
+
+LOCAL_SHARED_LIBRARIES := libandroid liblog libnativehelper libOpenSLES libutils
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/media/libaudiojni/Blob.h b/tests/tests/media/libaudiojni/Blob.h
new file mode 100644
index 0000000..134232c
--- /dev/null
+++ b/tests/tests/media/libaudiojni/Blob.h
@@ -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.
+ */
+
+#ifndef ANDROID_BLOB_H
+#define ANDROID_BLOB_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace android {
+
+// read only byte buffer like object
+
+class BlobReadOnly {
+public:
+ BlobReadOnly(const void *data, size_t size, bool byReference) :
+ mMem(byReference ? NULL : malloc(size)),
+ mData(byReference ? data : mMem),
+ mSize(size) {
+ if (!byReference) {
+ memcpy(mMem, data, size);
+ }
+ }
+ ~BlobReadOnly() {
+ free(mMem);
+ }
+
+private:
+ void * const mMem;
+
+public:
+ const void * const mData;
+ const size_t mSize;
+};
+
+// read/write byte buffer like object
+
+class Blob {
+public:
+ Blob(size_t size) :
+ mData(malloc(size)),
+ mOffset(0),
+ mSize(size),
+ mMem(mData) { }
+
+ // by reference
+ Blob(void *data, size_t size) :
+ mData(data),
+ mOffset(0),
+ mSize(size),
+ mMem(NULL) { }
+
+ ~Blob() {
+ free(mMem);
+ }
+
+ void * const mData;
+ size_t mOffset;
+ const size_t mSize;
+
+private:
+ void * const mMem;
+};
+
+} // namespace android
+
+#endif // ANDROID_BLOB_H
diff --git a/tests/tests/media/libaudiojni/Gate.h b/tests/tests/media/libaudiojni/Gate.h
new file mode 100644
index 0000000..dfc15b7
--- /dev/null
+++ b/tests/tests/media/libaudiojni/Gate.h
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_GATE_H
+#define ANDROID_GATE_H
+
+#include <stdint.h>
+#include <mutex>
+
+namespace android {
+
+// Gate is a synchronization object.
+//
+// Threads will pass if it is open.
+// Threads will block (wait) if it is closed.
+//
+// When a gate is opened, all waiting threads will pass through.
+//
+// Since gate holds no external locks, consistency with external
+// state needs to be handled elsewhere.
+//
+// We use mWaitCount to indicate the number of threads that have
+// arrived at the gate via wait(). Each thread entering
+// wait obtains a unique waitId (which is the current mWaitCount).
+// This can be viewed as a sequence number.
+//
+// We use mPassCount to indicate the number of threads that have
+// passed the gate. If the waitId is less than or equal to the mPassCount
+// then that thread has passed the gate. An open gate sets mPassedCount
+// to the current mWaitCount, allowing all prior threads to pass.
+//
+// See sync_timeline, sync_pt, etc. for graphics.
+
+class Gate {
+public:
+ Gate(bool open = false) :
+ mOpen(open),
+ mExit(false),
+ mWaitCount(0),
+ mPassCount(0)
+ { }
+
+ // waits for the gate to open, returns immediately if gate is already open.
+ //
+ // Do not hold a monitor lock while calling this.
+ //
+ // returns true if we passed the gate normally
+ // false if gate is terminated and we didn't pass the gate.
+ bool wait() {
+ std::unique_lock<std::mutex> l(mLock);
+ size_t waitId = ++mWaitCount;
+ if (mOpen) {
+ mPassCount = waitId; // let me through
+ }
+ while (!passedGate_l(waitId) && !mExit) {
+ mCondition.wait(l);
+ }
+ return passedGate_l(waitId);
+ }
+
+ // close the gate.
+ void closeGate() {
+ std::lock_guard<std::mutex> l(mLock);
+ mOpen = false;
+ mExit = false;
+ }
+
+ // open the gate.
+ // signal to all waiters it is okay to go.
+ void openGate() {
+ std::lock_guard<std::mutex> l(mLock);
+ mOpen = true;
+ mExit = false;
+ if (waiters_l() > 0) {
+ mPassCount = mWaitCount; // allow waiting threads to go through
+ // unoptimized pthreads will wake thread to find we still hold lock.
+ mCondition.notify_all();
+ }
+ }
+
+ // terminate (term has expired).
+ // all threads allowed to pass regardless of whether the gate is open or closed.
+ void terminate() {
+ std::lock_guard<std::mutex> l(mLock);
+ mExit = true;
+ if (waiters_l() > 0) {
+ // unoptimized pthreads will wake thread to find we still hold lock.
+ mCondition.notify_all();
+ }
+ }
+
+ bool isOpen() {
+ std::lock_guard<std::mutex> l(mLock);
+ return mOpen;
+ }
+
+ // return how many waiters are at the gate.
+ size_t waiters() {
+ std::lock_guard<std::mutex> l(mLock);
+ return waiters_l();
+ }
+
+private:
+ bool mOpen;
+ bool mExit;
+ size_t mWaitCount; // total number of threads that have called wait()
+ size_t mPassCount; // total number of threads passed the gate.
+ std::condition_variable mCondition;
+ std::mutex mLock;
+
+ // return how many waiters are at the gate.
+ inline size_t waiters_l() {
+ return mWaitCount - mPassCount;
+ }
+
+ // return whether the waitId (from mWaitCount) has passed through the gate
+ inline bool passedGate_l(size_t waitId) {
+ return (ssize_t)(waitId - mPassCount) <= 0;
+ }
+};
+
+} // namespace android
+
+#endif // ANDROID_GATE_H
diff --git a/tests/tests/media/libaudiojni/appendix-b-1-1-buffer-queue.cpp b/tests/tests/media/libaudiojni/appendix-b-1-1-buffer-queue.cpp
new file mode 100644
index 0000000..5bb88a7
--- /dev/null
+++ b/tests/tests/media/libaudiojni/appendix-b-1-1-buffer-queue.cpp
@@ -0,0 +1,250 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "OpenSL-ES-Test-B-1-1-Buffer-Queue"
+
+#include "sl-utils.h"
+
+/*
+ * See https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
+ * Appendix B.1.1 sample code.
+ *
+ * Minor edits made to conform to Android coding style.
+ *
+ * Correction to code: SL_IID_VOLUME is now made optional for the mixer.
+ * It isn't supported on the standard Android mixer, but it is supported on the player.
+ */
+
+#define MAX_NUMBER_INTERFACES 3
+
+/* Local storage for Audio data in 16 bit words */
+#define AUDIO_DATA_STORAGE_SIZE 4096
+
+#define AUDIO_DATA_SEGMENTS 8
+
+/* Audio data buffer size in 16 bit words. 8 data segments are used in
+ this simple example */
+#define AUDIO_DATA_BUFFER_SIZE (AUDIO_DATA_STORAGE_SIZE / AUDIO_DATA_SEGMENTS)
+
+/* Structure for passing information to callback function */
+typedef struct {
+ SLPlayItf playItf;
+ SLint16 *pDataBase; // Base address of local audio data storage
+ SLint16 *pData; // Current address of local audio data storage
+ SLuint32 size;
+} CallbackCntxt;
+
+/* Local storage for Audio data */
+static SLint16 pcmData[AUDIO_DATA_STORAGE_SIZE];
+
+/* Callback for Buffer Queue events */
+static void BufferQueueCallback(
+ SLBufferQueueItf queueItf,
+ void *pContext)
+{
+ SLresult res;
+ CallbackCntxt *pCntxt = (CallbackCntxt*)pContext;
+ if (pCntxt->pData < (pCntxt->pDataBase + pCntxt->size)) {
+ res = (*queueItf)->Enqueue(queueItf, (void *)pCntxt->pData,
+ sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+ ALOGE_IF(res != SL_RESULT_SUCCESS, "error: %s", android::getSLErrStr(res));
+ /* Increase data pointer by buffer size */
+ pCntxt->pData += AUDIO_DATA_BUFFER_SIZE;
+ }
+}
+
+/* Play some music from a buffer queue */
+static void TestPlayMusicBufferQueue(SLObjectItf sl)
+{
+ SLEngineItf EngineItf;
+
+ SLresult res;
+
+ SLDataSource audioSource;
+ SLDataLocator_BufferQueue bufferQueue;
+ SLDataFormat_PCM pcm;
+
+ SLDataSink audioSink;
+ SLDataLocator_OutputMix locator_outputmix;
+
+ SLObjectItf player;
+ SLPlayItf playItf;
+ SLBufferQueueItf bufferQueueItf;
+ SLBufferQueueState state;
+
+ SLObjectItf OutputMix;
+ SLVolumeItf volumeItf;
+
+ int i;
+
+ SLboolean required[MAX_NUMBER_INTERFACES];
+ SLInterfaceID iidArray[MAX_NUMBER_INTERFACES];
+
+ /* Callback context for the buffer queue callback function */
+ CallbackCntxt cntxt;
+
+ /* Get the SL Engine Interface which is implicit */
+ res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void *)&EngineItf);
+ CheckErr(res);
+
+ /* Initialize arrays required[] and iidArray[] */
+ for (i = 0; i < MAX_NUMBER_INTERFACES; i++) {
+ required[i] = SL_BOOLEAN_FALSE;
+ iidArray[i] = SL_IID_NULL;
+ }
+
+ // Set arrays required[] and iidArray[] for VOLUME interface
+ required[0] = SL_BOOLEAN_FALSE; // ANDROID: we don't require this interface
+ iidArray[0] = SL_IID_VOLUME;
+
+#if 0
+ const unsigned interfaces = 1;
+#else
+
+ /* FIXME: Android doesn't properly support optional interfaces (required == false).
+ [3.1.6] When an application requests explicit interfaces during object creation,
+ it can flag any interface as required. If an implementation is unable to satisfy
+ the request for an interface that is not flagged as required (i.e. it is not required),
+ this will not cause the object to fail creation. On the other hand, if the interface
+ is flagged as required and the implementation is unable to satisfy the request
+ for the interface, the object will not be created.
+ */
+ const unsigned interfaces = 0;
+#endif
+ // Create Output Mix object to be used by player
+ res = (*EngineItf)->CreateOutputMix(EngineItf, &OutputMix, interfaces,
+ iidArray, required);
+ CheckErr(res);
+
+ // Realizing the Output Mix object in synchronous mode.
+ res = (*OutputMix)->Realize(OutputMix, SL_BOOLEAN_FALSE);
+ CheckErr(res);
+
+ volumeItf = NULL; // ANDROID: Volume interface on mix object may not be supported
+ res = (*OutputMix)->GetInterface(OutputMix, SL_IID_VOLUME,
+ (void *)&volumeItf);
+
+ /* Setup the data source structure for the buffer queue */
+ bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
+ bufferQueue.numBuffers = 4; /* Four buffers in our buffer queue */
+
+ /* Setup the format of the content in the buffer queue */
+ pcm.formatType = SL_DATAFORMAT_PCM;
+ pcm.numChannels = 2;
+ pcm.samplesPerSec = SL_SAMPLINGRATE_44_1;
+ pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ pcm.containerSize = 16;
+ pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+ pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+ audioSource.pFormat = (void *)&pcm;
+ audioSource.pLocator = (void *)&bufferQueue;
+
+ /* Setup the data sink structure */
+ locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+ locator_outputmix.outputMix = OutputMix;
+ audioSink.pLocator = (void *)&locator_outputmix;
+ audioSink.pFormat = NULL;
+
+ /* Initialize the context for Buffer queue callbacks */
+ cntxt.pDataBase = pcmData;
+ cntxt.pData = cntxt.pDataBase;
+ cntxt.size = sizeof(pcmData) / sizeof(pcmData[0]); // ANDROID: Bug
+
+ /* Set arrays required[] and iidArray[] for SEEK interface
+ (PlayItf is implicit) */
+ required[0] = SL_BOOLEAN_TRUE;
+ iidArray[0] = SL_IID_BUFFERQUEUE;
+
+ /* Create the music player */
+
+ res = (*EngineItf)->CreateAudioPlayer(EngineItf, &player,
+ &audioSource, &audioSink, 1, iidArray, required);
+ CheckErr(res);
+
+ /* Realizing the player in synchronous mode. */
+ res = (*player)->Realize(player, SL_BOOLEAN_FALSE);
+ CheckErr(res);
+
+ /* Get seek and play interfaces */
+ res = (*player)->GetInterface(player, SL_IID_PLAY, (void *)&playItf);
+ CheckErr(res);
+ res = (*player)->GetInterface(player, SL_IID_BUFFERQUEUE,
+ (void *)&bufferQueueItf);
+ CheckErr(res);
+
+ /* Setup to receive buffer queue event callbacks */
+ res = (*bufferQueueItf)->RegisterCallback(bufferQueueItf,
+ BufferQueueCallback, &cntxt /* BUG, was NULL */);
+ CheckErr(res);
+
+ /* Before we start set volume to -3dB (-300mB) */
+ if (volumeItf != NULL) { // ANDROID: Volume interface may not be supported.
+ res = (*volumeItf)->SetVolumeLevel(volumeItf, -300);
+ CheckErr(res);
+ }
+
+ /* Enqueue a few buffers to get the ball rolling */
+ res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
+ sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+ CheckErr(res);
+ cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
+ res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
+ sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+ CheckErr(res);
+ cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
+ res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
+ sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+ CheckErr(res);
+ cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
+
+ /* Play the PCM samples using a buffer queue */
+ res = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
+ CheckErr(res);
+
+ /* Wait until the PCM data is done playing, the buffer queue callback
+ will continue to queue buffers until the entire PCM data has been
+ played. This is indicated by waiting for the count member of the
+ SLBufferQueueState to go to zero.
+ */
+ res = (*bufferQueueItf)->GetState(bufferQueueItf, &state);
+ CheckErr(res);
+
+ while (state.count) {
+ usleep(5 * 1000 /* usec */); // ANDROID: avoid busy waiting
+ (*bufferQueueItf)->GetState(bufferQueueItf, &state);
+ }
+
+ /* Make sure player is stopped */
+ res = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);
+ CheckErr(res);
+
+ /* Destroy the player */
+ (*player)->Destroy(player);
+
+ /* Destroy Output Mix object */
+ (*OutputMix)->Destroy(OutputMix);
+}
+
+extern "C" void Java_android_media_cts_AudioNativeTest_nativeAppendixBBufferQueue(
+ JNIEnv * /* env */, jclass /* clazz */)
+{
+ SLObjectItf engineObject = android::OpenSLEngine();
+ LOG_ALWAYS_FATAL_IF(engineObject == NULL, "cannot open OpenSL ES engine");
+
+ TestPlayMusicBufferQueue(engineObject);
+ android::CloseSLEngine(engineObject);
+}
diff --git a/tests/tests/media/libaudiojni/appendix-b-1-2-recording.cpp b/tests/tests/media/libaudiojni/appendix-b-1-2-recording.cpp
new file mode 100644
index 0000000..5f6f3aa
--- /dev/null
+++ b/tests/tests/media/libaudiojni/appendix-b-1-2-recording.cpp
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "OpenSL-ES-Test-B-1-2-Recording"
+
+#include "sl-utils.h"
+
+/*
+ * See https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
+ * Appendix B.1.2 sample code.
+ *
+ * Minor edits made to conform to Android coding style.
+ *
+ * Correction to code: SL_IID_AUDIOIODEVICECAPABILITIES is not supported.
+ * Detection of microphone should be made in Java layer.
+ */
+
+#define MAX_NUMBER_INTERFACES 5
+#define MAX_NUMBER_INPUT_DEVICES 3
+#define POSITION_UPDATE_PERIOD 1000 /* 1 sec */
+
+static void RecordEventCallback(SLRecordItf caller __unused,
+ void *pContext __unused,
+ SLuint32 recordevent __unused)
+{
+ /* Callback code goes here */
+}
+
+/*
+ * Test recording of audio from a microphone into a specified file
+ */
+static void TestAudioRecording(SLObjectItf sl)
+{
+ SLObjectItf recorder;
+ SLRecordItf recordItf;
+ SLEngineItf EngineItf;
+ SLAudioIODeviceCapabilitiesItf AudioIODeviceCapabilitiesItf;
+ SLAudioInputDescriptor AudioInputDescriptor;
+ SLresult res;
+
+ SLDataSource audioSource;
+ SLDataLocator_IODevice locator_mic;
+ SLDeviceVolumeItf devicevolumeItf;
+ SLDataSink audioSink;
+ SLDataLocator_URI uri;
+ SLDataFormat_MIME mime;
+
+ int i;
+ SLboolean required[MAX_NUMBER_INTERFACES];
+ SLInterfaceID iidArray[MAX_NUMBER_INTERFACES];
+
+ SLuint32 InputDeviceIDs[MAX_NUMBER_INPUT_DEVICES];
+ SLint32 numInputs = 0;
+ SLboolean mic_available = SL_BOOLEAN_FALSE;
+ SLuint32 mic_deviceID = 0;
+
+ /* Get the SL Engine Interface which is implicit */
+ res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void *)&EngineItf);
+ CheckErr(res);
+
+ AudioIODeviceCapabilitiesItf = NULL;
+ /* Get the Audio IO DEVICE CAPABILITIES interface, which is also
+ implicit */
+ res = (*sl)->GetInterface(sl, SL_IID_AUDIOIODEVICECAPABILITIES,
+ (void *)&AudioIODeviceCapabilitiesItf);
+ // ANDROID: obtaining SL_IID_AUDIOIODEVICECAPABILITIES may fail
+ if (AudioIODeviceCapabilitiesItf != NULL ) {
+ numInputs = MAX_NUMBER_INPUT_DEVICES;
+ res = (*AudioIODeviceCapabilitiesItf)->GetAvailableAudioInputs(
+ AudioIODeviceCapabilitiesItf, &numInputs, InputDeviceIDs);
+ CheckErr(res);
+ /* Search for either earpiece microphone or headset microphone input
+ device - with a preference for the latter */
+ for (i = 0; i < numInputs; i++) {
+ res = (*AudioIODeviceCapabilitiesItf)->QueryAudioInputCapabilities(
+ AudioIODeviceCapabilitiesItf, InputDeviceIDs[i], &AudioInputDescriptor);
+ CheckErr(res);
+ if ((AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_ATTACHED_WIRED)
+ && (AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER)
+ && (AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HEADSET)) {
+ mic_deviceID = InputDeviceIDs[i];
+ mic_available = SL_BOOLEAN_TRUE;
+ break;
+ }
+ else if ((AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_INTEGRATED)
+ && (AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER)
+ && (AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HANDSET)) {
+ mic_deviceID = InputDeviceIDs[i];
+ mic_available = SL_BOOLEAN_TRUE;
+ break;
+ }
+ }
+ } else {
+ mic_deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+ mic_available = true;
+ }
+
+ /* If neither of the preferred input audio devices is available, no
+ point in continuing */
+ if (!mic_available) {
+ /* Appropriate error message here */
+ ALOGW("No microphone available");
+ return;
+ }
+
+ /* Initialize arrays required[] and iidArray[] */
+ for (i = 0; i < MAX_NUMBER_INTERFACES; i++) {
+ required[i] = SL_BOOLEAN_FALSE;
+ iidArray[i] = SL_IID_NULL;
+ }
+
+ // ANDROID: the following may fail for volume
+ devicevolumeItf = NULL;
+ /* Get the optional DEVICE VOLUME interface from the engine */
+ res = (*sl)->GetInterface(sl, SL_IID_DEVICEVOLUME,
+ (void *)&devicevolumeItf);
+
+ /* Set recording volume of the microphone to -3 dB */
+ if (devicevolumeItf != NULL) { // ANDROID: Volume may not be supported
+ res = (*devicevolumeItf)->SetVolume(devicevolumeItf, mic_deviceID, -300);
+ CheckErr(res);
+ }
+
+ /* Setup the data source structure */
+ locator_mic.locatorType = SL_DATALOCATOR_IODEVICE;
+ locator_mic.deviceType = SL_IODEVICE_AUDIOINPUT;
+ locator_mic.deviceID = mic_deviceID;
+ locator_mic.device= NULL;
+
+ audioSource.pLocator = (void *)&locator_mic;
+ audioSource.pFormat = NULL;
+
+#if 0
+ /* Setup the data sink structure */
+ uri.locatorType = SL_DATALOCATOR_URI;
+ uri.URI = (SLchar *) "file:///recordsample.wav";
+ mime.formatType = SL_DATAFORMAT_MIME;
+ mime.mimeType = (SLchar *) "audio/x-wav";
+ mime.containerType = SL_CONTAINERTYPE_WAV;
+ audioSink.pLocator = (void *)&uri;
+ audioSink.pFormat = (void *)&mime;
+#else
+ // FIXME: Android requires SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
+ // because the recorder makes the distinction from SL_DATALOCATOR_BUFFERQUEUE
+ // which the player does not.
+ SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2
+ };
+ SLDataFormat_PCM format_pcm = {
+ SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,
+ SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
+ SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN
+ };
+ audioSink = { &loc_bq, &format_pcm };
+#endif
+
+ /* Create audio recorder */
+ res = (*EngineItf)->CreateAudioRecorder(EngineItf, &recorder,
+ &audioSource, &audioSink, 0, iidArray, required);
+ CheckErr(res);
+
+ /* Realizing the recorder in synchronous mode. */
+ res = (*recorder)->Realize(recorder, SL_BOOLEAN_FALSE);
+ CheckErr(res);
+
+ /* Get the RECORD interface - it is an implicit interface */
+ res = (*recorder)->GetInterface(recorder, SL_IID_RECORD, (void *)&recordItf);
+ CheckErr(res);
+
+ // ANDROID: Should register SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface for callback.
+ // but does original SL_DATALOCATOR_BUFFERQUEUE variant work just as well ?
+
+ /* Setup to receive position event callbacks */
+ res = (*recordItf)->RegisterCallback(recordItf, RecordEventCallback, NULL);
+ CheckErr(res);
+
+ /* Set notifications to occur after every second - may be useful in
+ updating a recording progress bar */
+ res = (*recordItf)->SetPositionUpdatePeriod(recordItf, POSITION_UPDATE_PERIOD);
+ CheckErr(res);
+ res = (*recordItf)->SetCallbackEventsMask(recordItf, SL_RECORDEVENT_HEADATNEWPOS);
+ CheckErr(res);
+
+ /* Set the duration of the recording - 30 seconds (30,000
+ milliseconds) */
+ res = (*recordItf)->SetDurationLimit(recordItf, 30000);
+ CheckErr(res);
+
+ /* Record the audio */
+ res = (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_RECORDING);
+ CheckErr(res);
+
+ // ANDROID: BUG - we don't wait for anything to record!
+
+ /* Destroy the recorder object */
+ (*recorder)->Destroy(recorder);
+}
+
+extern "C" void Java_android_media_cts_AudioNativeTest_nativeAppendixBRecording(
+ JNIEnv * /* env */, jclass /* clazz */)
+{
+ SLObjectItf engineObject = android::OpenSLEngine();
+ LOG_ALWAYS_FATAL_IF(engineObject == NULL, "cannot open OpenSL ES engine");
+
+ TestAudioRecording(engineObject);
+ android::CloseSLEngine(engineObject);
+}
diff --git a/tests/tests/media/libaudiojni/audio-record-native.cpp b/tests/tests/media/libaudiojni/audio-record-native.cpp
new file mode 100644
index 0000000..9103cdc
--- /dev/null
+++ b/tests/tests/media/libaudiojni/audio-record-native.cpp
@@ -0,0 +1,631 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio-record-native"
+
+#include "Blob.h"
+#include "Gate.h"
+#include "sl-utils.h"
+
+#include <deque>
+#include <utils/Errors.h>
+
+// Select whether to use STL shared pointer or to use Android strong pointer.
+// We really don't promote any sharing of this object for its lifetime, but nevertheless could
+// change the shared pointer value on the fly if desired.
+#define USE_SHARED_POINTER
+
+#ifdef USE_SHARED_POINTER
+#include <memory>
+template <typename T> using shared_pointer = std::shared_ptr<T>;
+#else
+#include <utils/RefBase.h>
+template <typename T> using shared_pointer = android::sp<T>;
+#endif
+
+using namespace android;
+
+// Must be kept in sync with Java android.media.cts.AudioRecordNative.ReadFlags
+enum {
+ READ_FLAG_BLOCKING = (1 << 0),
+};
+
+// buffer queue buffers on the OpenSL ES side.
+// The choice can be >= 1. There is also internal buffering by AudioRecord.
+
+static const size_t BUFFER_SIZE_MSEC = 20;
+
+// TODO: Add a single buffer blocking read mode which does not require additional memory.
+// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
+
+class AudioRecordNative
+#ifndef USE_SHARED_POINTER
+ : public RefBase // android strong pointers require RefBase
+#endif
+{
+public:
+ AudioRecordNative() :
+ mEngineObj(NULL),
+ mEngine(NULL),
+ mRecordObj(NULL),
+ mRecord(NULL),
+ mBufferQueue(NULL),
+ mRecordState(SL_RECORDSTATE_STOPPED),
+ mBufferSize(0),
+ mNumBuffers(0)
+ { }
+
+ ~AudioRecordNative() {
+ close();
+ }
+
+ typedef std::lock_guard<std::recursive_mutex> auto_lock;
+
+ status_t open(uint32_t numChannels, uint32_t sampleRate, bool useFloat, uint32_t numBuffers) {
+ close();
+ auto_lock l(mLock);
+ mEngineObj = OpenSLEngine();
+ if (mEngineObj == NULL) {
+ ALOGW("cannot create OpenSL ES engine");
+ return INVALID_OPERATION;
+ }
+
+ SLresult res;
+ for (;;) {
+ /* Get the SL Engine Interface which is implicit */
+ res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ SLDataLocator_IODevice locator_mic;
+ /* Setup the data source structure */
+ locator_mic.locatorType = SL_DATALOCATOR_IODEVICE;
+ locator_mic.deviceType = SL_IODEVICE_AUDIOINPUT;
+ locator_mic.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+ locator_mic.device= NULL;
+ SLDataSource audioSource;
+ audioSource.pLocator = (void *)&locator_mic;
+ audioSource.pFormat = NULL;
+
+ // FIXME: Android requires SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
+ // because the recorder makes the distinction from SL_DATALOCATOR_BUFFERQUEUE
+ // which the player does not.
+ SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, numBuffers
+ };
+#if 0
+ SLDataFormat_PCM pcm = {
+ SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,
+ SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
+ SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN
+ };
+#else
+ SLAndroidDataFormat_PCM_EX pcm;
+ pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
+ pcm.numChannels = numChannels;
+ pcm.sampleRate = sampleRate * 1000;
+ pcm.bitsPerSample = useFloat ?
+ SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
+ pcm.containerSize = pcm.bitsPerSample;
+ pcm.channelMask = channelCountToMask(numChannels);
+ pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+ // additional
+ pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
+ : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+#endif
+ SLDataSink audioSink;
+ audioSink = { &loc_bq, &pcm };
+
+ SLboolean required[2];
+ SLInterfaceID iidArray[2];
+ /* Request the AndroidSimpleBufferQueue and AndroidConfiguration interfaces */
+ required[0] = SL_BOOLEAN_TRUE;
+ iidArray[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
+ required[1] = SL_BOOLEAN_TRUE;
+ iidArray[1] = SL_IID_ANDROIDCONFIGURATION;
+
+ ALOGV("creating recorder");
+ /* Create audio recorder */
+ res = (*mEngine)->CreateAudioRecorder(mEngine, &mRecordObj,
+ &audioSource, &audioSink, 2, iidArray, required);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ ALOGV("realizing recorder");
+ /* Realizing the recorder in synchronous mode. */
+ res = (*mRecordObj)->Realize(mRecordObj, SL_BOOLEAN_FALSE /* async */);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ ALOGV("geting record interface");
+ /* Get the RECORD interface - it is an implicit interface */
+ res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_RECORD, (void *)&mRecord);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ ALOGV("geting buffer queue interface");
+ /* Get the buffer queue interface which was explicitly requested */
+ res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ (void *)&mBufferQueue);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ ALOGV("registering buffer queue interface");
+ /* Setup to receive buffer queue event callbacks */
+ res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ mBufferSize = (BUFFER_SIZE_MSEC * sampleRate / 1000)
+ * numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
+ mNumBuffers = numBuffers;
+ // success
+ break;
+ }
+ if (res != SL_RESULT_SUCCESS) {
+ close(); // should be safe to close even with lock held
+ ALOGW("open error %s", android::getSLErrStr(res));
+ return INVALID_OPERATION;
+ }
+ return OK;
+ }
+
+ void close() {
+ SLObjectItf engineObj;
+ SLObjectItf recordObj;
+ {
+ auto_lock l(mLock);
+ (void)stop();
+ // once stopped, we can unregister the callback
+ if (mBufferQueue != NULL) {
+ (void)(*mBufferQueue)->RegisterCallback(
+ mBufferQueue, NULL /* callback */, NULL /* *pContext */);
+ }
+ (void)flush();
+ engineObj = mEngineObj;
+ recordObj = mRecordObj;
+ // clear out interfaces and objects
+ mRecord = NULL;
+ mBufferQueue = NULL;
+ mEngine = NULL;
+ mRecordObj = NULL;
+ mEngineObj = NULL;
+ mRecordState = SL_RECORDSTATE_STOPPED;
+ mBufferSize = 0;
+ mNumBuffers = 0;
+ }
+ // destroy without lock
+ if (recordObj != NULL) {
+ (*recordObj)->Destroy(recordObj);
+ }
+ if (engineObj) {
+ CloseSLEngine(engineObj);
+ }
+ }
+
+ status_t setRecordState(SLuint32 recordState) {
+ auto_lock l(mLock);
+ if (mRecord == NULL) {
+ return INVALID_OPERATION;
+ }
+ if (recordState == SL_RECORDSTATE_RECORDING) {
+ queueBuffers();
+ }
+ SLresult res = (*mRecord)->SetRecordState(mRecord, recordState);
+ if (res != SL_RESULT_SUCCESS) {
+ ALOGW("setRecordState %d error %s", recordState, android::getSLErrStr(res));
+ return INVALID_OPERATION;
+ }
+ mRecordState = recordState;
+ return OK;
+ }
+
+ SLuint32 getRecordState() {
+ auto_lock l(mLock);
+ if (mRecord == NULL) {
+ return SL_RECORDSTATE_STOPPED;
+ }
+ SLuint32 recordState;
+ SLresult res = (*mRecord)->GetRecordState(mRecord, &recordState);
+ if (res != SL_RESULT_SUCCESS) {
+ ALOGW("getRecordState error %s", android::getSLErrStr(res));
+ return SL_RECORDSTATE_STOPPED;
+ }
+ return recordState;
+ }
+
+ status_t getPositionInMsec(int64_t *position) {
+ auto_lock l(mLock);
+ if (mRecord == NULL) {
+ return INVALID_OPERATION;
+ }
+ if (position == NULL) {
+ return BAD_VALUE;
+ }
+ SLuint32 pos;
+ SLresult res = (*mRecord)->GetPosition(mRecord, &pos);
+ if (res != SL_RESULT_SUCCESS) {
+ ALOGW("getPosition error %s", android::getSLErrStr(res));
+ return INVALID_OPERATION;
+ }
+ // only lower 32 bits valid
+ *position = pos;
+ return OK;
+ }
+
+ status_t start() {
+ return setRecordState(SL_RECORDSTATE_RECORDING);
+ }
+
+ status_t pause() {
+ return setRecordState(SL_RECORDSTATE_PAUSED);
+ }
+
+ status_t stop() {
+ return setRecordState(SL_RECORDSTATE_STOPPED);
+ }
+
+ status_t flush() {
+ auto_lock l(mLock);
+ status_t result = OK;
+ if (mBufferQueue != NULL) {
+ SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
+ if (res != SL_RESULT_SUCCESS) {
+ return INVALID_OPERATION;
+ }
+ }
+ mReadyQueue.clear();
+ // possible race if the engine is in the callback
+ // safety is only achieved if the recorder is paused or stopped.
+ mDeliveredQueue.clear();
+ mReadBlob = NULL;
+ mReadReady.terminate();
+ return result;
+ }
+
+ ssize_t read(void *buffer, size_t size, bool blocking = false) {
+ std::lock_guard<std::mutex> rl(mReadLock);
+ // not needed if we assume that a single thread is doing the reading
+ // or we always operate in non-blocking mode.
+
+ ALOGV("reading:%p %zu", buffer, size);
+ size_t copied;
+ std::shared_ptr<Blob> blob;
+ {
+ auto_lock l(mLock);
+ if (mEngine == NULL) {
+ return INVALID_OPERATION;
+ }
+ size_t osize = size;
+ while (!mReadyQueue.empty() && size > 0) {
+ auto b = mReadyQueue.front();
+ size_t tocopy = min(size, b->mSize - b->mOffset);
+ // ALOGD("buffer:%p size:%zu b->mSize:%zu b->mOffset:%zu tocopy:%zu ",
+ // buffer, size, b->mSize, b->mOffset, tocopy);
+ memcpy(buffer, (char *)b->mData + b->mOffset, tocopy);
+ buffer = (char *)buffer + tocopy;
+ size -= tocopy;
+ b->mOffset += tocopy;
+ if (b->mOffset == b->mSize) {
+ mReadyQueue.pop_front();
+ }
+ }
+ copied = osize - size;
+ if (!blocking || size == 0 || mReadBlob.get() != NULL) {
+ return copied;
+ }
+ blob = std::make_shared<Blob>(buffer, size);
+ mReadBlob = blob;
+ mReadReady.closeGate(); // the callback will open gate when read is completed.
+ }
+ if (mReadReady.wait()) {
+ // success then the blob is ours with valid data otherwise a flush has occurred
+ // and we return a short count.
+ copied += blob->mOffset;
+ }
+ return copied;
+ }
+
+ void logBufferState() {
+ auto_lock l(mLock);
+ SLBufferQueueState state;
+ SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
+ CheckErr(res);
+ ALOGD("logBufferState state.count:%d state.playIndex:%d", state.count, state.playIndex);
+ }
+
+ size_t getBuffersPending() {
+ auto_lock l(mLock);
+ return mReadyQueue.size();
+ }
+
+private:
+ status_t queueBuffers() {
+ if (mBufferQueue == NULL) {
+ return INVALID_OPERATION;
+ }
+ if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
+ // add new empty buffer
+ auto b = std::make_shared<Blob>(mBufferSize);
+ mDeliveredQueue.emplace_back(b);
+ (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
+ }
+ return OK;
+ }
+
+ void bufferQueueCallback(SLBufferQueueItf queueItf) {
+ auto_lock l(mLock);
+ if (queueItf != mBufferQueue) {
+ ALOGW("invalid buffer queue interface, ignoring");
+ return;
+ }
+ // logBufferState();
+
+ // remove from delivered queue
+ if (mDeliveredQueue.size()) {
+ auto b = mDeliveredQueue.front();
+ mDeliveredQueue.pop_front();
+ if (mReadBlob.get() != NULL) {
+ size_t tocopy = min(mReadBlob->mSize - mReadBlob->mOffset, b->mSize - b->mOffset);
+ memcpy((char *)mReadBlob->mData + mReadBlob->mOffset,
+ (char *)b->mData + b->mOffset, tocopy);
+ b->mOffset += tocopy;
+ mReadBlob->mOffset += tocopy;
+ if (mReadBlob->mOffset == mReadBlob->mSize) {
+ mReadBlob = NULL; // we're done, clear our reference.
+ mReadReady.openGate(); // allow read to continue.
+ }
+ if (b->mOffset == b->mSize) {
+ b = NULL;
+ }
+ }
+ if (b.get() != NULL) {
+ if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
+ mReadyQueue.emplace_back(b); // save onto ready queue for future reads
+ } else {
+ ALOGW("dropping data");
+ }
+ }
+ } else {
+ ALOGW("no delivered data!");
+ }
+ queueBuffers();
+ }
+
+ static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
+ SLresult res;
+ // naked native record
+ AudioRecordNative *record = (AudioRecordNative *)pContext;
+ record->bufferQueueCallback(queueItf);
+ }
+
+ SLObjectItf mEngineObj;
+ SLEngineItf mEngine;
+ SLObjectItf mRecordObj;
+ SLRecordItf mRecord;
+ SLBufferQueueItf mBufferQueue;
+ SLuint32 mRecordState;
+ size_t mBufferSize;
+ size_t mNumBuffers;
+ std::recursive_mutex mLock; // monitor lock - locks public API methods and callback.
+ // recursive since it may call itself through API.
+ std::mutex mReadLock; // read lock - for blocking mode, prevents multiple
+ // reader threads from overlapping reads. this is
+ // generally unnecessary as reads occur from
+ // one thread only. acquire this before mLock.
+ std::shared_ptr<Blob> mReadBlob;
+ Gate mReadReady;
+ std::deque<std::shared_ptr<Blob>> mReadyQueue; // ready for read.
+ std::deque<std::shared_ptr<Blob>> mDeliveredQueue; // delivered to BufferQueue
+};
+
+/* Java static methods.
+ *
+ * These are not directly exposed to the user, so we can assume a valid "jrecord" handle
+ * to be passed in.
+ */
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeTest(
+ JNIEnv * /* env */, jclass /* clazz */,
+ jint numChannels, jint sampleRate, jboolean useFloat,
+ jint msecPerBuffer, jint numBuffers)
+{
+ AudioRecordNative record;
+ const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
+ const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;
+
+ status_t res;
+ void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
+ for (;;) {
+ res = record.open(numChannels, sampleRate, useFloat, numBuffers);
+ if (res != OK) break;
+
+ record.logBufferState();
+ res = record.start();
+ if (res != OK) break;
+
+ size_t size = framesPerBuffer * numBuffers * frameSize;
+ for (size_t offset = 0; size - offset > 0; ) {
+ ssize_t amount = record.read((char *)buffer + offset, size -offset);
+ // ALOGD("read amount: %zd", amount);
+ if (amount < 0) break;
+ offset += amount;
+ usleep(5 * 1000 /* usec */);
+ }
+
+ res = record.stop();
+ break;
+ }
+ record.close();
+ free(buffer);
+ return res;
+}
+
+extern "C" jlong Java_android_media_cts_AudioRecordNative_nativeCreateRecord(
+ JNIEnv * /* env */, jclass /* clazz */)
+{
+ return (jlong)(new shared_pointer<AudioRecordNative>(new AudioRecordNative()));
+}
+
+extern "C" void Java_android_media_cts_AudioRecordNative_nativeDestroyRecord(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+ delete (shared_pointer<AudioRecordNative> *)jrecord;
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeOpen(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jrecord,
+ jint numChannels, jint sampleRate, jboolean useFloat, jint numBuffers)
+{
+ auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+ if (record.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)record->open(numChannels, sampleRate, useFloat == JNI_TRUE,
+ numBuffers);
+}
+
+extern "C" void Java_android_media_cts_AudioRecordNative_nativeClose(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+ auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+ if (record.get() != NULL) {
+ record->close();
+ }
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeStart(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+ auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+ if (record.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)record->start();
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeStop(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+ auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+ if (record.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)record->stop();
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativePause(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+ auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+ if (record.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)record->pause();
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeFlush(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+ auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+ if (record.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)record->flush();
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeGetPositionInMsec(
+ JNIEnv *env, jclass /* clazz */, jlong jrecord, jlongArray jPosition)
+{
+ auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+ if (record.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ int64_t pos;
+ status_t res = record->getPositionInMsec(&pos);
+ if (res != OK) {
+ return res;
+ }
+ jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
+ if (nPostition == NULL) {
+ ALOGE("Unable to get array for nativeGetPositionInMsec()");
+ return BAD_VALUE;
+ }
+ nPostition[0] = (jlong)pos;
+ env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
+ return OK;
+}
+
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeGetBuffersPending(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+ auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+ if (record.get() == NULL) {
+ return (jint)0;
+ }
+ return (jint)record->getBuffersPending();
+}
+
+template <typename T>
+static inline jint readFromRecord(jlong jrecord, T *data,
+ jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+ auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+ if (record.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+
+ const bool isBlocking = readFlags & READ_FLAG_BLOCKING;
+ const size_t sizeInBytes = sizeInSamples * sizeof(T);
+ ssize_t ret = record->read(data + offsetInSamples, sizeInBytes, isBlocking == JNI_TRUE);
+ return (jint)(ret > 0 ? ret / sizeof(T) : ret);
+}
+
+template <typename T>
+static inline jint readArray(JNIEnv *env, jclass /* clazz */, jlong jrecord,
+ T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+ if (javaAudioData == NULL) {
+ return (jint)BAD_VALUE;
+ }
+
+ auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
+ if (cAudioData == NULL) {
+ ALOGE("Error retrieving destination of audio data to record");
+ return (jint)BAD_VALUE;
+ }
+
+ jint ret = readFromRecord(jrecord, cAudioData, offsetInSamples, sizeInSamples, readFlags);
+ envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
+ return ret;
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadByteArray(
+ JNIEnv *env, jclass clazz, jlong jrecord,
+ jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+ return readArray(env, clazz, jrecord, byteArray, offsetInSamples, sizeInSamples, readFlags);
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadShortArray(
+ JNIEnv *env, jclass clazz, jlong jrecord,
+ jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+ return readArray(env, clazz, jrecord, shortArray, offsetInSamples, sizeInSamples, readFlags);
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadFloatArray(
+ JNIEnv *env, jclass clazz, jlong jrecord,
+ jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+ return readArray(env, clazz, jrecord, floatArray, offsetInSamples, sizeInSamples, readFlags);
+}
diff --git a/tests/tests/media/libaudiojni/audio-track-native.cpp b/tests/tests/media/libaudiojni/audio-track-native.cpp
new file mode 100644
index 0000000..d51a751
--- /dev/null
+++ b/tests/tests/media/libaudiojni/audio-track-native.cpp
@@ -0,0 +1,579 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio-track-native"
+
+#include "Blob.h"
+#include "Gate.h"
+#include "sl-utils.h"
+
+#include <deque>
+#include <utils/Errors.h>
+
+// Select whether to use STL shared pointer or to use Android strong pointer.
+// We really don't promote any sharing of this object for its lifetime, but nevertheless could
+// change the shared pointer value on the fly if desired.
+#define USE_SHARED_POINTER
+
+#ifdef USE_SHARED_POINTER
+#include <memory>
+template <typename T> using shared_pointer = std::shared_ptr<T>;
+#else
+#include <utils/RefBase.h>
+template <typename T> using shared_pointer = android::sp<T>;
+#endif
+
+using namespace android;
+
+// Must be kept in sync with Java android.media.cts.AudioTrackNative.WriteFlags
+enum {
+ WRITE_FLAG_BLOCKING = (1 << 0),
+};
+
+// TODO: Add a single buffer blocking write mode which does not require additional memory.
+// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
+
+class AudioTrackNative
+#ifndef USE_SHARED_POINTER
+ : public RefBase // android strong pointers require RefBase
+#endif
+{
+public:
+ AudioTrackNative() :
+ mEngineObj(NULL),
+ mEngine(NULL),
+ mOutputMixObj(NULL),
+ mPlayerObj(NULL),
+ mPlay(NULL),
+ mBufferQueue(NULL),
+ mPlayState(SL_PLAYSTATE_STOPPED),
+ mNumBuffers(0)
+ { }
+
+ ~AudioTrackNative() {
+ close();
+ }
+
+ typedef std::lock_guard<std::recursive_mutex> auto_lock;
+
+ status_t open(uint32_t numChannels, uint32_t sampleRate, bool useFloat,
+ uint32_t numBuffers) {
+ close();
+ auto_lock l(mLock);
+ mEngineObj = OpenSLEngine();
+ if (mEngineObj == NULL) {
+ ALOGW("cannot create OpenSL ES engine");
+ return INVALID_OPERATION;
+ }
+
+ SLresult res;
+ for (;;) {
+ /* Get the SL Engine Interface which is implicit */
+ res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ // Create Output Mix object to be used by player
+ res = (*mEngine)->CreateOutputMix(
+ mEngine, &mOutputMixObj, 0 /* numInterfaces */,
+ NULL /* pInterfaceIds */, NULL /* pInterfaceRequired */);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ // Realizing the Output Mix object in synchronous mode.
+ res = (*mOutputMixObj)->Realize(mOutputMixObj, SL_BOOLEAN_FALSE /* async */);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ /* Setup the data source structure for the buffer queue */
+ SLDataLocator_BufferQueue bufferQueue;
+ bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
+ bufferQueue.numBuffers = numBuffers;
+ mNumBuffers = numBuffers;
+
+ /* Setup the format of the content in the buffer queue */
+
+ SLAndroidDataFormat_PCM_EX pcm;
+ pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
+ pcm.numChannels = numChannels;
+ pcm.sampleRate = sampleRate * 1000;
+ pcm.bitsPerSample = useFloat ?
+ SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
+ pcm.containerSize = pcm.bitsPerSample;
+ pcm.channelMask = channelCountToMask(numChannels);
+ pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+ // additional
+ pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
+ : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ SLDataSource audioSource;
+ audioSource.pFormat = (void *)&pcm;
+ audioSource.pLocator = (void *)&bufferQueue;
+
+ /* Setup the data sink structure */
+ SLDataLocator_OutputMix locator_outputmix;
+ locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+ locator_outputmix.outputMix = mOutputMixObj;
+
+ SLDataSink audioSink;
+ audioSink.pLocator = (void *)&locator_outputmix;
+ audioSink.pFormat = NULL;
+
+ SLboolean required[1];
+ SLInterfaceID iidArray[1];
+ required[0] = SL_BOOLEAN_TRUE;
+ iidArray[0] = SL_IID_BUFFERQUEUE;
+
+ res = (*mEngine)->CreateAudioPlayer(mEngine, &mPlayerObj,
+ &audioSource, &audioSink, 1 /* numInterfaces */, iidArray, required);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ res = (*mPlayerObj)->Realize(mPlayerObj, SL_BOOLEAN_FALSE /* async */);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ res = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_PLAY, (void*)&mPlay);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ res = (*mPlayerObj)->GetInterface(
+ mPlayerObj, SL_IID_BUFFERQUEUE, (void*)&mBufferQueue);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ /* Setup to receive buffer queue event callbacks */
+ res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
+ if (res != SL_RESULT_SUCCESS) break;
+
+ // success
+ break;
+ }
+ if (res != SL_RESULT_SUCCESS) {
+ close(); // should be safe to close even with lock held
+ ALOGW("open error %s", android::getSLErrStr(res));
+ return INVALID_OPERATION;
+ }
+ return OK;
+ }
+
+ void close() {
+ SLObjectItf engineObj;
+ SLObjectItf outputMixObj;
+ SLObjectItf playerObj;
+ {
+ auto_lock l(mLock);
+ if (mPlay != NULL && mPlayState != SL_PLAYSTATE_STOPPED) {
+ (void)stop();
+ }
+ // once stopped, we can unregister the callback
+ if (mBufferQueue != NULL) {
+ (void)(*mBufferQueue)->RegisterCallback(
+ mBufferQueue, NULL /* callback */, NULL /* *pContext */);
+ }
+ (void)flush();
+ engineObj = mEngineObj;
+ outputMixObj = mOutputMixObj;
+ playerObj = mPlayerObj;
+ // clear out interfaces and objects
+ mPlay = NULL;
+ mBufferQueue = NULL;
+ mEngine = NULL;
+ mPlayerObj = NULL;
+ mOutputMixObj = NULL;
+ mEngineObj = NULL;
+ mPlayState = SL_PLAYSTATE_STOPPED;
+ }
+ // destroy without lock
+ if (playerObj != NULL) {
+ (*playerObj)->Destroy(playerObj);
+ }
+ if (outputMixObj != NULL) {
+ (*outputMixObj)->Destroy(outputMixObj);
+ }
+ if (engineObj != NULL) {
+ CloseSLEngine(engineObj);
+ }
+ }
+
+ status_t setPlayState(SLuint32 playState) {
+ auto_lock l(mLock);
+ if (mPlay == NULL) {
+ return INVALID_OPERATION;
+ }
+ SLresult res = (*mPlay)->SetPlayState(mPlay, playState);
+ if (res != SL_RESULT_SUCCESS) {
+ ALOGW("setPlayState %d error %s", playState, android::getSLErrStr(res));
+ return INVALID_OPERATION;
+ }
+ mPlayState = playState;
+ return OK;
+ }
+
+ SLuint32 getPlayState() {
+ auto_lock l(mLock);
+ if (mPlay == NULL) {
+ return SL_PLAYSTATE_STOPPED;
+ }
+ SLuint32 playState;
+ SLresult res = (*mPlay)->GetPlayState(mPlay, &playState);
+ if (res != SL_RESULT_SUCCESS) {
+ ALOGW("getPlayState error %s", android::getSLErrStr(res));
+ return SL_PLAYSTATE_STOPPED;
+ }
+ return playState;
+ }
+
+ status_t getPositionInMsec(int64_t *position) {
+ auto_lock l(mLock);
+ if (mPlay == NULL) {
+ return INVALID_OPERATION;
+ }
+ if (position == NULL) {
+ return BAD_VALUE;
+ }
+ SLuint32 pos;
+ SLresult res = (*mPlay)->GetPosition(mPlay, &pos);
+ if (res != SL_RESULT_SUCCESS) {
+ ALOGW("getPosition error %s", android::getSLErrStr(res));
+ return INVALID_OPERATION;
+ }
+ // only lower 32 bits valid
+ *position = pos;
+ return OK;
+ }
+
+ status_t start() {
+ return setPlayState(SL_PLAYSTATE_PLAYING);
+ }
+
+ status_t pause() {
+ return setPlayState(SL_PLAYSTATE_PAUSED);
+ }
+
+ status_t stop() {
+ return setPlayState(SL_PLAYSTATE_STOPPED);
+ }
+
+ status_t flush() {
+ auto_lock l(mLock);
+ status_t result = OK;
+ if (mBufferQueue != NULL) {
+ SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
+ if (res != SL_RESULT_SUCCESS) {
+ return INVALID_OPERATION;
+ }
+ }
+
+ // possible race if the engine is in the callback
+ // safety is only achieved if the player is paused or stopped.
+ mDeliveredQueue.clear();
+ return result;
+ }
+
+ status_t write(const void *buffer, size_t size, bool isBlocking = false) {
+ std::lock_guard<std::mutex> rl(mWriteLock);
+ // not needed if we assume that a single thread is doing the reading
+ // or we always operate in non-blocking mode.
+
+ {
+ auto_lock l(mLock);
+ if (mBufferQueue == NULL) {
+ return INVALID_OPERATION;
+ }
+ if (mDeliveredQueue.size() < mNumBuffers) {
+ auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
+ mDeliveredQueue.emplace_back(b);
+ (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
+ return size;
+ }
+ if (!isBlocking) {
+ return 0;
+ }
+ mWriteReady.closeGate(); // we're full.
+ }
+ if (mWriteReady.wait()) {
+ auto_lock l(mLock);
+ if (mDeliveredQueue.size() < mNumBuffers) {
+ auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
+ mDeliveredQueue.emplace_back(b);
+ (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
+ return size;
+ }
+ }
+ ALOGW("unable to deliver write");
+ return 0;
+ }
+
+ void logBufferState() {
+ auto_lock l(mLock);
+ SLBufferQueueState state;
+ SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
+ CheckErr(res);
+ ALOGD("logBufferState state.count:%d state.playIndex:%d", state.count, state.playIndex);
+ }
+
+ size_t getBuffersPending() {
+ auto_lock l(mLock);
+ return mDeliveredQueue.size();
+ }
+
+private:
+ void bufferQueueCallback(SLBufferQueueItf queueItf) {
+ auto_lock l(mLock);
+ if (queueItf != mBufferQueue) {
+ ALOGW("invalid buffer queue interface, ignoring");
+ return;
+ }
+ // logBufferState();
+
+ // remove from delivered queue
+ if (mDeliveredQueue.size()) {
+ mDeliveredQueue.pop_front();
+ } else {
+ ALOGW("no delivered data!");
+ }
+ if (!mWriteReady.isOpen()) {
+ mWriteReady.openGate();
+ }
+ }
+
+ static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
+ SLresult res;
+ // naked native track
+ AudioTrackNative *track = (AudioTrackNative *)pContext;
+ track->bufferQueueCallback(queueItf);
+ }
+
+ SLObjectItf mEngineObj;
+ SLEngineItf mEngine;
+ SLObjectItf mOutputMixObj;
+ SLObjectItf mPlayerObj;
+ SLPlayItf mPlay;
+ SLBufferQueueItf mBufferQueue;
+ SLuint32 mPlayState;
+ SLuint32 mNumBuffers;
+ std::recursive_mutex mLock; // monitor lock - locks public API methods and callback.
+ // recursive since it may call itself through API.
+ std::mutex mWriteLock; // write lock - for blocking mode, prevents multiple
+ // writer threads from overlapping writes. this is
+ // generally unnecessary as writes occur from
+ // one thread only. acquire this before mLock.
+ Gate mWriteReady;
+ std::deque<std::shared_ptr<BlobReadOnly>> mDeliveredQueue; // delivered to mBufferQueue
+};
+
+/* Java static methods.
+ *
+ * These are not directly exposed to the user, so we can assume a valid "jtrack" handle
+ * to be passed in.
+ */
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeTest(
+ JNIEnv * /* env */, jclass /* clazz */,
+ jint numChannels, jint sampleRate, jboolean useFloat,
+ jint msecPerBuffer, jint numBuffers)
+{
+ AudioTrackNative track;
+ const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
+ const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;
+
+ status_t res;
+ void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
+ for (;;) {
+ res = track.open(numChannels, sampleRate, useFloat, numBuffers);
+ if (res != OK) break;
+
+ for (int i = 0; i < numBuffers; ++i) {
+ track.write((char *)buffer + i * (framesPerBuffer * frameSize),
+ framesPerBuffer * frameSize);
+ }
+
+ track.logBufferState();
+ res = track.start();
+ if (res != OK) break;
+
+ size_t buffers;
+ while ((buffers = track.getBuffersPending()) > 0) {
+ // ALOGD("outstanding buffers: %zu", buffers);
+ usleep(5 * 1000 /* usec */);
+ }
+ res = track.stop();
+ break;
+ }
+ track.close();
+ free(buffer);
+ return res;
+}
+
+extern "C" jlong Java_android_media_cts_AudioTrackNative_nativeCreateTrack(
+ JNIEnv * /* env */, jclass /* clazz */)
+{
+ return (jlong)(new shared_pointer<AudioTrackNative>(new AudioTrackNative()));
+}
+
+extern "C" void Java_android_media_cts_AudioTrackNative_nativeDestroyTrack(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+ delete (shared_pointer<AudioTrackNative> *)jtrack;
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeOpen(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jtrack,
+ jint numChannels, jint sampleRate, jboolean useFloat, jint numBuffers)
+{
+ auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+ if (track.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)track->open(numChannels, sampleRate, useFloat == JNI_TRUE,
+ numBuffers);
+}
+
+extern "C" void Java_android_media_cts_AudioTrackNative_nativeClose(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+ auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+ if (track.get() != NULL) {
+ track->close();
+ }
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStart(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+ auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+ if (track.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)track->start();
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStop(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+ auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+ if (track.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)track->stop();
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativePause(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+ auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+ if (track.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)track->pause();
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeFlush(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+ auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+ if (track.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ return (jint)track->flush();
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetPositionInMsec(
+ JNIEnv *env, jclass /* clazz */, jlong jtrack, jlongArray jPosition)
+{
+ auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+ if (track.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+ int64_t pos;
+ status_t res = track->getPositionInMsec(&pos);
+ if (res != OK) {
+ return res;
+ }
+ jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
+ if (nPostition == NULL) {
+ ALOGE("Unable to get array for nativeGetPositionInMsec()");
+ return BAD_VALUE;
+ }
+ nPostition[0] = (jlong)pos;
+ env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
+ return OK;
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetBuffersPending(
+ JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+ auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+ if (track.get() == NULL) {
+ return (jint)0;
+ }
+ return (jint)track->getBuffersPending();
+}
+
+template <typename T>
+static inline jint writeToTrack(jlong jtrack, const T *data,
+ jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+ auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+ if (track.get() == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+
+ const bool isBlocking = writeFlags & WRITE_FLAG_BLOCKING;
+ const size_t sizeInBytes = sizeInSamples * sizeof(T);
+ ssize_t ret = track->write(data + offsetInSamples, sizeInBytes, isBlocking);
+ return (jint)(ret > 0 ? ret / sizeof(T) : ret);
+}
+
+template <typename T>
+static inline jint writeArray(JNIEnv *env, jclass /* clazz */, jlong jtrack,
+ T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+ if (javaAudioData == NULL) {
+ return (jint)INVALID_OPERATION;
+ }
+
+ auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
+ if (cAudioData == NULL) {
+ ALOGE("Error retrieving source of audio data to play");
+ return (jint)BAD_VALUE;
+ }
+
+ jint ret = writeToTrack(jtrack, cAudioData, offsetInSamples, sizeInSamples, writeFlags);
+ envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
+ return ret;
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteByteArray(
+ JNIEnv *env, jclass clazz, jlong jtrack,
+ jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+ ALOGV("nativeWriteByteArray(%p, %d, %d, %d)",
+ byteArray, offsetInSamples, sizeInSamples, writeFlags);
+ return writeArray(env, clazz, jtrack, byteArray, offsetInSamples, sizeInSamples, writeFlags);
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteShortArray(
+ JNIEnv *env, jclass clazz, jlong jtrack,
+ jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+ ALOGV("nativeWriteShortArray(%p, %d, %d, %d)",
+ shortArray, offsetInSamples, sizeInSamples, writeFlags);
+ return writeArray(env, clazz, jtrack, shortArray, offsetInSamples, sizeInSamples, writeFlags);
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteFloatArray(
+ JNIEnv *env, jclass clazz, jlong jtrack,
+ jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+ ALOGV("nativeWriteFloatArray(%p, %d, %d, %d)",
+ floatArray, offsetInSamples, sizeInSamples, writeFlags);
+ return writeArray(env, clazz, jtrack, floatArray, offsetInSamples, sizeInSamples, writeFlags);
+}
diff --git a/tests/tests/media/libaudiojni/sl-utils.cpp b/tests/tests/media/libaudiojni/sl-utils.cpp
new file mode 100644
index 0000000..1aa89ba
--- /dev/null
+++ b/tests/tests/media/libaudiojni/sl-utils.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SL-Utils"
+
+#include "sl-utils.h"
+#include <utils/Mutex.h>
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+// These will wind up in <SLES/OpenSLES_Android.h>
+#define SL_ANDROID_SPEAKER_QUAD (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT \
+ | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT)
+
+#define SL_ANDROID_SPEAKER_5DOT1 (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT \
+ | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY| SL_SPEAKER_BACK_LEFT \
+ | SL_SPEAKER_BACK_RIGHT)
+
+#define SL_ANDROID_SPEAKER_7DOT1 (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT \
+ |SL_SPEAKER_SIDE_RIGHT)
+
+namespace android {
+
+static Mutex gLock;
+static SLObjectItf gEngineObject;
+static unsigned gRefCount;
+
+static const char *gErrorStrings[] = {
+ "SL_RESULT_SUCCESS", // 0
+ "SL_RESULT_PRECONDITIONS_VIOLATE", // 1
+ "SL_RESULT_PARAMETER_INVALID", // 2
+ "SL_RESULT_MEMORY_FAILURE", // 3
+ "SL_RESULT_RESOURCE_ERROR", // 4
+ "SL_RESULT_RESOURCE_LOST", // 5
+ "SL_RESULT_IO_ERROR", // 6
+ "SL_RESULT_BUFFER_INSUFFICIENT", // 7
+ "SL_RESULT_CONTENT_CORRUPTED", // 8
+ "SL_RESULT_CONTENT_UNSUPPORTED", // 9
+ "SL_RESULT_CONTENT_NOT_FOUND", // 10
+ "SL_RESULT_PERMISSION_DENIED", // 11
+ "SL_RESULT_FEATURE_UNSUPPORTED", // 12
+ "SL_RESULT_INTERNAL_ERROR", // 13
+ "SL_RESULT_UNKNOWN_ERROR", // 14
+ "SL_RESULT_OPERATION_ABORTED", // 15
+ "SL_RESULT_CONTROL_LOST", // 16
+};
+
+const char *getSLErrStr(int code) {
+ if ((size_t)code >= ARRAY_SIZE(gErrorStrings)) {
+ return "SL_RESULT_UNKNOWN";
+ }
+ return gErrorStrings[code];
+}
+
+SLuint32 channelCountToMask(unsigned channelCount) {
+ switch (channelCount) {
+ case 1:
+ return SL_SPEAKER_FRONT_LEFT; // we prefer left over center
+ case 2:
+ return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+ case 3:
+ return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER;
+ case 4:
+ return SL_ANDROID_SPEAKER_QUAD;
+ case 5:
+ return SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER;
+ case 6:
+ return SL_ANDROID_SPEAKER_5DOT1;
+ case 7:
+ return SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_BACK_CENTER;
+ case 8:
+ return SL_ANDROID_SPEAKER_7DOT1;
+ default:
+ return 0;
+ }
+}
+
+static SLObjectItf createEngine() {
+ static SLEngineOption EngineOption[] = {
+ (SLuint32) SL_ENGINEOPTION_THREADSAFE,
+ (SLuint32) SL_BOOLEAN_TRUE
+ };
+ // create engine in thread-safe mode
+ SLObjectItf engine;
+ SLresult result = slCreateEngine(&engine,
+ 1 /* numOptions */, EngineOption /* pEngineOptions */,
+ 0 /* numInterfaces */, NULL /* pInterfaceIds */, NULL /* pInterfaceRequired */);
+ if (result != SL_RESULT_SUCCESS) {
+ ALOGE("slCreateEngine() failed: %s", getSLErrStr(result));
+ return NULL;
+ }
+ // realize the engine
+ result = (*engine)->Realize(engine, SL_BOOLEAN_FALSE /* async */);
+ if (result != SL_RESULT_SUCCESS) {
+ ALOGE("Realize() failed: %s", getSLErrStr(result));
+ (*engine)->Destroy(engine);
+ return NULL;
+ }
+ return engine;
+}
+
+SLObjectItf OpenSLEngine(bool global) {
+
+ if (!global) {
+ return createEngine();
+ }
+ Mutex::Autolock l(gLock);
+ if (gRefCount == 0) {
+ gEngineObject = createEngine();
+ }
+ gRefCount++;
+ return gEngineObject;
+}
+
+void CloseSLEngine(SLObjectItf engine) {
+ Mutex::Autolock l(gLock);
+ if (engine == gEngineObject) {
+ if (gRefCount == 0) {
+ ALOGE("CloseSLEngine(%p): refcount already 0", engine);
+ return;
+ }
+ if (--gRefCount != 0) {
+ return;
+ }
+ gEngineObject = NULL;
+ }
+ (*engine)->Destroy(engine);
+}
+
+} // namespace android
+
diff --git a/tests/tests/media/libaudiojni/sl-utils.h b/tests/tests/media/libaudiojni/sl-utils.h
new file mode 100644
index 0000000..8582648
--- /dev/null
+++ b/tests/tests/media/libaudiojni/sl-utils.h
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_SL_UTILS_H
+#define ANDROID_SL_UTILS_H
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+#include <jni.h>
+#include <mutex>
+#include <utils/Log.h>
+
+#define CheckErr(res) LOG_ALWAYS_FATAL_IF( \
+ (res) != SL_RESULT_SUCCESS, "result error %s", android::getSLErrStr(res));
+
+namespace android {
+
+// FIXME: Move to common file.
+template <typename T>
+static inline
+const T &min(const T &a, const T &b) {
+ return a < b ? a : b;
+}
+
+/* Returns the error string for the OpenSL ES error code
+ */
+const char *getSLErrStr(int code);
+
+/* Returns the OpenSL ES equivalent standard channel mask
+ * for a given channel count, 0 if no such mask is available.
+ */
+SLuint32 channelCountToMask(unsigned channelCount);
+
+/* Returns an OpenSL ES Engine object interface.
+ * The engine created will be thread safe [3.2]
+ * The underlying implementation may not support more than one engine. [4.1.1]
+ *
+ * @param global if true, return and open the global engine instance or make
+ * a local engine instance if false.
+ * @return NULL if unsuccessful or the Engine SLObjectItf.
+ */
+SLObjectItf OpenSLEngine(bool global = true);
+
+/* Closes an OpenSL ES Engine object returned by OpenSLEngine().
+ */
+void CloseSLEngine(SLObjectItf engine);
+
+// overloaded JNI array helper functions (same as in android_media_AudioRecord)
+inline
+jbyte *envGetArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy) {
+ return env->GetByteArrayElements(array, isCopy);
+}
+
+inline
+void envReleaseArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode) {
+ env->ReleaseByteArrayElements(array, elems, mode);
+}
+
+inline
+jshort *envGetArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy) {
+ return env->GetShortArrayElements(array, isCopy);
+}
+
+inline
+void envReleaseArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode) {
+ env->ReleaseShortArrayElements(array, elems, mode);
+}
+
+inline
+jfloat *envGetArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy) {
+ return env->GetFloatArrayElements(array, isCopy);
+}
+
+inline
+void envReleaseArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode) {
+ env->ReleaseFloatArrayElements(array, elems, mode);
+}
+
+} // namespace android
+
+#endif // ANDROID_SL_UTILS_H
diff --git a/tests/tests/media/src/android/media/cts/AudioHelper.java b/tests/tests/media/src/android/media/cts/AudioHelper.java
index efee024..6707ea6 100644
--- a/tests/tests/media/src/android/media/cts/AudioHelper.java
+++ b/tests/tests/media/src/android/media/cts/AudioHelper.java
@@ -211,13 +211,13 @@
* This affects AudioRecord timing.
*/
public static class AudioRecordAudit extends AudioRecord {
- AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
+ public AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
int format, int bufferSize, boolean isChannelIndex) {
this(audioSource, sampleRate, channelMask, format, bufferSize, isChannelIndex,
AudioManager.STREAM_MUSIC, 500 /*delayMs*/);
}
- AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
+ public AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
int format, int bufferSize,
boolean isChannelIndex, int auditStreamType, int delayMs) {
// without channel index masks, one could call:
@@ -408,4 +408,84 @@
private int mPosition;
private long mFinishAtMs;
}
+
+ /* AudioRecordAudit extends AudioRecord to allow concurrent playback
+ * of read content to an AudioTrack. This is for testing only.
+ * For general applications, it is NOT recommended to extend AudioRecord.
+ * This affects AudioRecord timing.
+ */
+ public static class AudioRecordAuditNative extends AudioRecordNative {
+ public AudioRecordAuditNative() {
+ super();
+ // Caution: delayMs too large results in buffer sizes that cannot be created.
+ mTrack = new AudioTrackNative();
+ }
+
+ @Override
+ public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
+ if (super.open(numChannels, sampleRate, useFloat, numBuffers)) {
+ if (!mTrack.open(numChannels, sampleRate, useFloat, 2 /* numBuffers */)) {
+ mTrack = null; // remove track
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ if (mTrack != null) {
+ mTrack.close();
+ }
+ }
+
+ @Override
+ public boolean start() {
+ if (super.start()) {
+ if (mTrack != null) {
+ mTrack.start();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean stop() {
+ if (super.stop()) {
+ if (mTrack != null) {
+ mTrack.stop(); // doesn't allow remaining data to play out
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readFlags) {
+ int samples = super.read(audioData, offsetInShorts, sizeInShorts, readFlags);
+ if (mTrack != null) {
+ Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples,
+ AudioTrackNative.WRITE_FLAG_BLOCKING));
+ mPosition += samples / mTrack.getChannelCount();
+ }
+ return samples;
+ }
+
+ @Override
+ public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readFlags) {
+ int samples = super.read(audioData, offsetInFloats, sizeInFloats, readFlags);
+ if (mTrack != null) {
+ Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples,
+ AudioTrackNative.WRITE_FLAG_BLOCKING));
+ mPosition += samples / mTrack.getChannelCount();
+ }
+ return samples;
+ }
+
+ public AudioTrackNative mTrack;
+ private final static String TAG = "AudioRecordAuditNative";
+ private int mPosition;
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/AudioNativeTest.java b/tests/tests/media/src/android/media/cts/AudioNativeTest.java
new file mode 100644
index 0000000..b10da0c
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioNativeTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.media.cts;
+
+import android.content.pm.PackageManager;
+import android.cts.util.CtsAndroidTestCase;
+import android.util.Log;
+
+public class AudioNativeTest extends CtsAndroidTestCase {
+ public void testAppendixBBufferQueue() {
+ nativeAppendixBBufferQueue();
+ }
+
+ public void testAppendixBRecording() {
+ // better to detect presence of microphone here.
+ if (!hasMicrophone()) {
+ return;
+ }
+ nativeAppendixBRecording();
+ }
+
+ public void testStereo16Playback() {
+ assertTrue(AudioTrackNative.test(
+ 2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
+ 20 /* msecPerBuffer */, 8 /* numBuffers */));
+ }
+
+ public void testStereo16Record() {
+ assertTrue(AudioRecordNative.test(
+ 2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
+ 20 /* msecPerBuffer */, 8 /* numBuffers */));
+ }
+
+ public void testPlayStreamData() throws Exception {
+ final String TEST_NAME = "testPlayStreamData";
+ final boolean TEST_FLOAT_ARRAY[] = {
+ false,
+ true,
+ };
+ // due to downmixer algorithmic latency, source channels greater than 2 may
+ // sound shorter in duration at 4kHz sampling rate.
+ final int TEST_SR_ARRAY[] = {
+ /* 4000, */ // below limit of OpenSL ES
+ 12345, // irregular sampling rate
+ 44100,
+ 48000,
+ 96000,
+ 192000,
+ };
+ // OpenSL ES Bug: MNC does not support channel counts of 3, 5, 7.
+ final int TEST_CHANNELS_ARRAY[] = {
+ 1,
+ 2,
+ // 3,
+ 4,
+ // 5,
+ 6,
+ // 7,
+ // 8 // can fail due to memory issues
+ };
+ final float TEST_SWEEP = 0; // sine wave only
+ final int TEST_TIME_IN_MSEC = 300;
+ final int TOLERANCE_MSEC = 20;
+
+ for (boolean TEST_FLOAT : TEST_FLOAT_ARRAY) {
+ double frequency = 400; // frequency changes for each test
+ for (int TEST_SR : TEST_SR_ARRAY) {
+ for (int TEST_CHANNELS : TEST_CHANNELS_ARRAY) {
+ // OpenSL ES BUG: we run out of AudioTrack memory for this config on MNC
+ // Log.d(TEST_NAME, "open channels:" + TEST_CHANNELS + " sr:" + TEST_SR);
+ if (TEST_FLOAT == true && TEST_CHANNELS >= 6 && TEST_SR >= 192000) {
+ continue;
+ }
+ AudioTrackNative track = new AudioTrackNative();
+ assertTrue(TEST_NAME,
+ track.open(TEST_CHANNELS, TEST_SR, TEST_FLOAT, 1 /* numBuffers */));
+ assertTrue(TEST_NAME, track.start());
+
+ final int sourceSamples =
+ (int)((long)TEST_SR * TEST_TIME_IN_MSEC * TEST_CHANNELS / 1000);
+ final double testFrequency = frequency / TEST_CHANNELS;
+ if (TEST_FLOAT) {
+ float data[] = AudioHelper.createSoundDataInFloatArray(
+ sourceSamples, TEST_SR,
+ testFrequency, TEST_SWEEP);
+ assertEquals(sourceSamples,
+ track.write(data, 0 /* offset */, sourceSamples,
+ AudioTrackNative.WRITE_FLAG_BLOCKING));
+ } else {
+ short data[] = AudioHelper.createSoundDataInShortArray(
+ sourceSamples, TEST_SR,
+ testFrequency, TEST_SWEEP);
+ assertEquals(sourceSamples,
+ track.write(data, 0 /* offset */, sourceSamples,
+ AudioTrackNative.WRITE_FLAG_BLOCKING));
+ }
+
+ while (true) {
+ // OpenSL ES BUG: getPositionInMsec returns 0 after a data underrun.
+
+ long position = track.getPositionInMsec();
+ //Log.d(TEST_NAME, "position: " + position[0]);
+ if (position >= (long)(TEST_TIME_IN_MSEC - TOLERANCE_MSEC)) {
+ break;
+ }
+
+ // It is safer to use a buffer count of 0 to determine termination
+ if (track.getBuffersPending() == 0) {
+ break;
+ }
+ Thread.sleep(5 /* millis */);
+ }
+ track.stop();
+ track.close();
+ Thread.sleep(40 /* millis */); // put a gap in the tone sequence
+ frequency += 50; // increment test tone frequency
+ }
+ }
+ }
+ }
+
+ public void testRecordStreamData() throws Exception {
+ final String TEST_NAME = "testRecordStreamData";
+ final boolean TEST_FLOAT_ARRAY[] = {
+ false,
+ true,
+ };
+ final int TEST_SR_ARRAY[] = {
+ //4000, // below limit of OpenSL ES
+ 12345, // irregular sampling rate
+ 44100,
+ 48000,
+ 96000,
+ 192000,
+ };
+ final int TEST_CHANNELS_ARRAY[] = {
+ 1,
+ 2,
+ // 3,
+ 4,
+ // 5,
+ 6,
+ // 7,
+ 8,
+ };
+ final int SEGMENT_DURATION_IN_MSEC = 20;
+ final int NUMBER_SEGMENTS = 10;
+
+ for (boolean TEST_FLOAT : TEST_FLOAT_ARRAY) {
+ for (int TEST_SR : TEST_SR_ARRAY) {
+ for (int TEST_CHANNELS : TEST_CHANNELS_ARRAY) {
+ // OpenSL ES BUG: we run out of AudioTrack memory for this config on MNC
+ if (TEST_FLOAT == true && TEST_CHANNELS >= 8 && TEST_SR >= 192000) {
+ continue;
+ }
+ AudioRecordNative record = new AudioRecordNative();
+ doRecordTest(record, TEST_CHANNELS, TEST_SR, TEST_FLOAT,
+ SEGMENT_DURATION_IN_MSEC, NUMBER_SEGMENTS);
+ }
+ }
+ }
+ }
+
+ public void testRecordAudit() throws Exception {
+ AudioRecordNative record = new AudioHelper.AudioRecordAuditNative();
+ doRecordTest(record, 4 /* numChannels */, 44100 /* sampleRate */, false /* useFloat */,
+ 1000 /* segmentDurationMs */, 10 /* numSegments */);
+ }
+
+ static {
+ System.loadLibrary("audio_jni");
+ }
+
+ private static final String TAG = "AudioNativeTest";
+
+ private void doRecordTest(AudioRecordNative record,
+ int numChannels, int sampleRate, boolean useFloat,
+ int segmentDurationMs, int numSegments) {
+ final String TEST_NAME = "doRecordTest";
+ try {
+ // Log.d(TEST_NAME, "open numChannels:" + numChannels + " sampleRate:" + sampleRate);
+ assertTrue(TEST_NAME, record.open(numChannels, sampleRate, useFloat,
+ numSegments /* numBuffers */));
+ assertTrue(TEST_NAME, record.start());
+
+ final int sourceSamples =
+ (int)((long)sampleRate * segmentDurationMs * numChannels / 1000);
+
+ if (useFloat) {
+ float data[] = new float[sourceSamples];
+ for (int i = 0; i < numSegments; ++i) {
+ assertEquals(sourceSamples,
+ record.read(data, 0 /* offset */, sourceSamples,
+ AudioRecordNative.READ_FLAG_BLOCKING));
+ }
+ } else {
+ short data[] = new short[sourceSamples];
+ for (int i = 0; i < numSegments; ++i) {
+ assertEquals(sourceSamples,
+ record.read(data, 0 /* offset */, sourceSamples,
+ AudioRecordNative.READ_FLAG_BLOCKING));
+ }
+ }
+ assertTrue(TEST_NAME, record.stop());
+ } finally {
+ record.close();
+ }
+ }
+
+ private boolean hasMicrophone() {
+ return getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_MICROPHONE);
+ }
+
+ private static native void nativeAppendixBBufferQueue();
+ private static native void nativeAppendixBRecording();
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordNative.java b/tests/tests/media/src/android/media/cts/AudioRecordNative.java
new file mode 100644
index 0000000..18df8ee
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioRecordNative.java
@@ -0,0 +1,153 @@
+/*
+ * 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.media.cts;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.cts.util.CtsAndroidTestCase;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class AudioRecordNative {
+ // Must be kept in sync with C++ JNI audio-record-native (AudioRecordNative) READ_FLAG_*
+ public static final int READ_FLAG_BLOCKING = 1 << 0;
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ READ_FLAG_BLOCKING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ReadFlags { }
+
+ public AudioRecordNative() {
+ mNativeRecordInJavaObj = nativeCreateRecord();
+ }
+
+ public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
+ if (nativeOpen(mNativeRecordInJavaObj, numChannels, sampleRate, useFloat, numBuffers)
+ == STATUS_OK) {
+ mChannelCount = numChannels;
+ return true;
+ }
+ return false;
+ }
+
+ public void close() {
+ nativeClose(mNativeRecordInJavaObj);
+ }
+
+ public boolean start() {
+ return nativeStart(mNativeRecordInJavaObj) == STATUS_OK;
+ }
+
+ public boolean stop() {
+ return nativeStop(mNativeRecordInJavaObj) == STATUS_OK;
+ }
+
+ public boolean pause() {
+ return nativePause(mNativeRecordInJavaObj) == STATUS_OK;
+ }
+
+ public boolean flush() {
+ return nativeFlush(mNativeRecordInJavaObj) == STATUS_OK;
+ }
+
+ public long getPositionInMsec() {
+ long[] position = new long[1];
+ if (nativeGetPositionInMsec(mNativeRecordInJavaObj, position) != STATUS_OK) {
+ throw new IllegalStateException();
+ }
+ return position[0];
+ }
+
+ public int getBuffersPending() {
+ return nativeGetBuffersPending(mNativeRecordInJavaObj);
+ }
+
+ public int read(@NonNull byte[] byteArray,
+ int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
+ return nativeReadByteArray(
+ mNativeRecordInJavaObj, byteArray, offsetInSamples, sizeInSamples, readFlags);
+ }
+
+ public int read(@NonNull short[] shortArray,
+ int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
+ return nativeReadShortArray(
+ mNativeRecordInJavaObj, shortArray, offsetInSamples, sizeInSamples, readFlags);
+ }
+
+ public int read(@NonNull float[] floatArray,
+ int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
+ return nativeReadFloatArray(
+ mNativeRecordInJavaObj, floatArray, offsetInSamples, sizeInSamples, readFlags);
+ }
+
+ public int getChannelCount() {
+ return mChannelCount;
+ }
+
+ public static boolean test(int numChannels, int sampleRate, boolean useFloat,
+ int msecPerBuffer, int numBuffers) {
+ return nativeTest(numChannels, sampleRate, useFloat, msecPerBuffer, numBuffers)
+ == STATUS_OK;
+ }
+
+ @Override
+ protected void finalize() {
+ nativeClose(mNativeRecordInJavaObj);
+ nativeDestroyRecord(mNativeRecordInJavaObj);
+ }
+
+ static {
+ System.loadLibrary("audio_jni");
+ }
+
+ private static final String TAG = "AudioRecordNative";
+ private int mChannelCount;
+ private final long mNativeRecordInJavaObj;
+ private static final int STATUS_OK = 0;
+
+ // static native API.
+ // The native API uses a long "record handle" created by nativeCreateRecord.
+ // The handle must be destroyed after use by nativeDestroyRecord.
+ //
+ // Return codes from the native layer are status_t.
+ // Converted to Java booleans or exceptions at the public API layer.
+ private static native long nativeCreateRecord();
+ private static native void nativeDestroyRecord(long record);
+ private static native int nativeOpen(
+ long record, int numChannels, int sampleRate, boolean useFloat, int numBuffers);
+ private static native void nativeClose(long record);
+ private static native int nativeStart(long record);
+ private static native int nativeStop(long record);
+ private static native int nativePause(long record);
+ private static native int nativeFlush(long record);
+ private static native int nativeGetPositionInMsec(long record, @NonNull long[] position);
+ private static native int nativeGetBuffersPending(long record);
+ private static native int nativeReadByteArray(long record, @NonNull byte[] byteArray,
+ int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
+ private static native int nativeReadShortArray(long record, @NonNull short[] shortArray,
+ int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
+ private static native int nativeReadFloatArray(long record, @NonNull float[] floatArray,
+ int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
+
+ // native interface for all-in-one testing, no record handle required.
+ private static native int nativeTest(
+ int numChannels, int sampleRate, boolean useFloat, int msecPerBuffer, int numBuffers);
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 968a382..b1ee3f5 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -698,6 +698,7 @@
// blank final variables: all successful paths will initialize the times.
final long endTime;
final long startTime;
+ final long stopRequestTime;
final long stopTime;
try {
@@ -850,6 +851,7 @@
// One must sleep to make sure the last event(s) come in.
Thread.sleep(30);
+ stopRequestTime = System.currentTimeMillis();
record.stop();
assertEquals(AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
@@ -911,10 +913,14 @@
// and there is no record.getPosition(), we consider only differential timing
// from the first marker or periodic event.
final int toleranceInFrames = TEST_SR * 80 / 1000; // 80 ms
+ final int testTimeInFrames = (int)((long)TEST_TIME_MS * TEST_SR / 1000);
AudioHelper.Statistics markerStat = new AudioHelper.Statistics();
for (int i = 1; i < markerList.size(); ++i) {
final int expected = mMarkerPeriodInFrames * i;
+ if (markerList.get(i) > testTimeInFrames) {
+ break; // don't consider any notifications when we might be stopping.
+ }
final int actual = markerList.get(i) - markerList.get(0);
//Log.d(TAG, "Marker: " + i + " expected(" + expected + ") actual(" + actual
// + ") diff(" + (actual - expected) + ")"
@@ -926,6 +932,9 @@
AudioHelper.Statistics periodicStat = new AudioHelper.Statistics();
for (int i = 1; i < periodicList.size(); ++i) {
final int expected = updatePeriodInFrames * i;
+ if (periodicList.get(i) > testTimeInFrames) {
+ break; // don't consider any notifications when we might be stopping.
+ }
final int actual = periodicList.get(i) - periodicList.get(0);
//Log.d(TAG, "Update: " + i + " expected(" + expected + ") actual(" + actual
// + ") diff(" + (actual - expected) + ")"
@@ -938,9 +947,11 @@
ReportLog log = getReportLog();
log.printValue(reportName + ": startRecording lag", firstSampleTime - startTime,
ResultType.LOWER_BETTER, ResultUnit.MS);
+ log.printValue(reportName + ": stop execution time", stopTime - stopRequestTime,
+ ResultType.LOWER_BETTER, ResultUnit.MS);
log.printValue(reportName + ": Total record time expected", TEST_TIME_MS,
ResultType.NEUTRAL, ResultUnit.MS);
- log.printValue(reportName + ": Total record time actual", (endTime - firstSampleTime),
+ log.printValue(reportName + ": Total record time actual", endTime - firstSampleTime,
ResultType.NEUTRAL, ResultUnit.MS);
log.printValue(reportName + ": Total markers expected", markerPeriods,
ResultType.NEUTRAL, ResultUnit.COUNT);
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackNative.java b/tests/tests/media/src/android/media/cts/AudioTrackNative.java
new file mode 100644
index 0000000..1ce44ef
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioTrackNative.java
@@ -0,0 +1,160 @@
+/*
+ * 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.media.cts;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.cts.util.CtsAndroidTestCase;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class AudioTrackNative {
+ // Must be kept in sync with C++ JNI audio-track-native (AudioTrackNative) WRITE_FLAG_*
+ public static final int WRITE_FLAG_BLOCKING = 1 << 0;
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ WRITE_FLAG_BLOCKING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WriteFlags { }
+
+ public AudioTrackNative() {
+ mNativeTrackInJavaObj = nativeCreateTrack();
+ }
+
+ // TODO: eventually accept AudioFormat
+ // numBuffers is the number of internal buffers before hitting the OpenSL ES.
+ // A value of 0 means that all writes are blocking.
+ public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
+ if (nativeOpen(mNativeTrackInJavaObj, numChannels, sampleRate, useFloat, numBuffers)
+ == STATUS_OK) {
+ mChannelCount = numChannels;
+ return true;
+ }
+ return false;
+ }
+
+ public void close() {
+ nativeClose(mNativeTrackInJavaObj);
+ }
+
+ public boolean start() {
+ return nativeStart(mNativeTrackInJavaObj) == STATUS_OK;
+ }
+
+ public boolean stop() {
+ return nativeStop(mNativeTrackInJavaObj) == STATUS_OK;
+ }
+
+ public boolean pause() {
+ return nativePause(mNativeTrackInJavaObj) == STATUS_OK;
+ }
+
+ public boolean flush() {
+ return nativeFlush(mNativeTrackInJavaObj) == STATUS_OK;
+ }
+
+ public long getPositionInMsec() {
+ long[] position = new long[1];
+ if (nativeGetPositionInMsec(mNativeTrackInJavaObj, position) != STATUS_OK) {
+ throw new IllegalStateException();
+ }
+ return position[0];
+ }
+
+ public int getBuffersPending() {
+ return nativeGetBuffersPending(mNativeTrackInJavaObj);
+ }
+
+ /* returns number of samples written.
+ * 0 may be returned if !isBlocking.
+ * negative value indicates error.
+ */
+ public int write(@NonNull byte[] byteArray,
+ int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
+ return nativeWriteByteArray(
+ mNativeTrackInJavaObj, byteArray, offsetInSamples, sizeInSamples, writeFlags);
+ }
+
+ public int write(@NonNull short[] shortArray,
+ int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
+ return nativeWriteShortArray(
+ mNativeTrackInJavaObj, shortArray, offsetInSamples, sizeInSamples, writeFlags);
+ }
+
+ public int write(@NonNull float[] floatArray,
+ int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
+ return nativeWriteFloatArray(
+ mNativeTrackInJavaObj, floatArray, offsetInSamples, sizeInSamples, writeFlags);
+ }
+
+ public int getChannelCount() {
+ return mChannelCount;
+ }
+
+ public static boolean test(int numChannels, int sampleRate, boolean useFloat,
+ int msecPerBuffer, int numBuffers) {
+ return nativeTest(numChannels, sampleRate, useFloat, msecPerBuffer, numBuffers)
+ == STATUS_OK;
+ }
+
+ @Override
+ protected void finalize() {
+ nativeClose(mNativeTrackInJavaObj);
+ nativeDestroyTrack(mNativeTrackInJavaObj);
+ }
+
+ static {
+ System.loadLibrary("audio_jni");
+ }
+
+ private static final String TAG = "AudioTrackNative";
+ private int mChannelCount;
+ private final long mNativeTrackInJavaObj;
+ private static final int STATUS_OK = 0;
+
+ // static native API.
+ // The native API uses a long "track handle" created by nativeCreateTrack.
+ // The handle must be destroyed after use by nativeDestroyTrack.
+ //
+ // Return codes from the native layer are status_t.
+ // Converted to Java booleans or exceptions at the public API layer.
+ private static native long nativeCreateTrack();
+ private static native void nativeDestroyTrack(long track);
+ private static native int nativeOpen(
+ long track, int numChannels, int sampleRate, boolean useFloat, int numBuffers);
+ private static native void nativeClose(long track);
+ private static native int nativeStart(long track);
+ private static native int nativeStop(long track);
+ private static native int nativePause(long track);
+ private static native int nativeFlush(long track);
+ private static native int nativeGetPositionInMsec(long track, @NonNull long[] position);
+ private static native int nativeGetBuffersPending(long track);
+ private static native int nativeWriteByteArray(long track, @NonNull byte[] byteArray,
+ int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
+ private static native int nativeWriteShortArray(long track, @NonNull short[] shortArray,
+ int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
+ private static native int nativeWriteFloatArray(long track, @NonNull float[] floatArray,
+ int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
+
+ // native interface for all-in-one testing, no track handle required.
+ private static native int nativeTest(
+ int numChannels, int sampleRate, boolean useFloat, int msecPerBuffer, int numBuffers);
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 15438e8..b3fcce1 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -549,13 +549,15 @@
MediaFormat format =
createMinFormat(mime, caps.getVideoCapabilities(), caps.colorFormats[0]);
Vector<MediaCodec> codecs = new Vector<MediaCodec>();
+ MediaCodec codec = null;
for (int i = 0; i < max; ++i) {
try {
Log.d(TAG, "Create codec " + name + " #" + i);
- MediaCodec codec = MediaCodec.createByCodecName(name);
+ codec = MediaCodec.createByCodecName(name);
codec.configure(format, null, null, flag);
codec.start();
codecs.add(codec);
+ codec = null;
} catch (IllegalArgumentException e) {
fail("Got unexpected IllegalArgumentException " + e.getMessage());
} catch (IOException e) {
@@ -569,12 +571,20 @@
} else {
fail("Unexpected CodecException " + e.getDiagnosticInfo());
}
+ } finally {
+ if (codec != null) {
+ Log.d(TAG, "release codec");
+ codec.release();
+ codec = null;
+ }
}
}
int actualMax = codecs.size();
for (int i = 0; i < codecs.size(); ++i) {
+ Log.d(TAG, "release codec #" + i);
codecs.get(i).release();
}
+ codecs.clear();
return actualMax;
}
@@ -587,6 +597,7 @@
}
public void testGetMaxSupportedInstances() {
+ final int MAX_INSTANCES = 32;
StringBuilder xmlOverrides = new StringBuilder();
MediaCodecList allCodecs = new MediaCodecList(MediaCodecList.ALL_CODECS);
for (MediaCodecInfo info : allCodecs.getCodecInfos()) {
@@ -603,7 +614,7 @@
if (shouldTestActual(caps)) {
int actualMax = getActualMax(
- info.isEncoder(), info.getName(), types[j], caps, max + 1);
+ info.isEncoder(), info.getName(), types[j], caps, MAX_INSTANCES);
Log.d(TAG, "actualMax " + actualMax + " vs reported max " + max);
if (actualMax < (int)(max * 0.9) || actualMax > (int) Math.ceil(max * 1.1)) {
String codec = "<MediaCodec name=\"" + info.getName() +
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index b6ee1db..ade790a 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -174,13 +174,29 @@
}
public void testRecorderCamera() throws Exception {
+ int width;
+ int height;
+ Camera camera = null;
if (!hasCamera()) {
return;
}
+ // Try to get camera first supported resolution.
+ // If we fail for any reason, set the video size to default value.
+ try {
+ camera = Camera.open();
+ width = camera.getParameters().getSupportedPreviewSizes().get(0).width;
+ height = camera.getParameters().getSupportedPreviewSizes().get(0).height;
+ } catch (Exception e) {
+ width = VIDEO_WIDTH;
+ height = VIDEO_HEIGHT;
+ }
+ if (camera != null) {
+ camera.release();
+ }
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
- mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
+ mMediaRecorder.setVideoSize(width, height);
mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS);
mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
mMediaRecorder.prepare();
diff --git a/tests/tests/media/src/android/media/cts/MediaSyncTest.java b/tests/tests/media/src/android/media/cts/MediaSyncTest.java
index b334040..c4fe4c1 100644
--- a/tests/tests/media/src/android/media/cts/MediaSyncTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSyncTest.java
@@ -33,6 +33,8 @@
import android.media.MediaTimestamp;
import android.media.PlaybackParams;
import android.media.SyncParams;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.view.Surface;
@@ -554,6 +556,9 @@
}
}
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+
Decoder(MediaSyncTest test, MediaSync sync, boolean isAudio) {
mMediaSyncTest = test;
mMediaSync = sync;
@@ -563,6 +568,10 @@
public boolean setup(int inputResourceId, Surface surface, long lastBufferTimestampUs) {
if (!mIsAudio) {
mSurface = surface;
+ // handle video callback in a separate thread as releaseOutputBuffer is blocking
+ mHandlerThread = new HandlerThread("SyncViewVidDec");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
}
mLastBufferTimestampUs = lastBufferTimestampUs;
try {
@@ -581,7 +590,7 @@
}
mDecoder = MediaCodec.createDecoderByType(mimeType);
mDecoder.configure(mediaFormat, mSurface, null, 0);
- mDecoder.setCallback(this);
+ mDecoder.setCallback(this, mHandler);
return true;
} catch (IOException e) {
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java b/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java
index f77b245..5595800 100644
--- a/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java
+++ b/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java
@@ -100,6 +100,7 @@
};
thread.start();
thread.join(20000 /* millis */);
+ System.gc();
Thread.sleep(5000); // give the gc a chance to release test activities.
boolean result = true;
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java
index 938324c..a11d773 100644
--- a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java
+++ b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java
@@ -31,26 +31,16 @@
super.onCreate(savedInstanceState);
moveTaskToBack(true);
- if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
- // haven't reached the limit with MAX_INSTANCES, report RESULT_OK directly and
- // skip additional test.
- finishWithResult(RESULT_OK);
- }
- useCodecs();
-
- boolean waitForReclaim = true;
Bundle extras = getIntent().getExtras();
if (extras != null) {
- waitForReclaim = extras.getBoolean("wait-for-reclaim", waitForReclaim);
+ mWaitForReclaim = extras.getBoolean("wait-for-reclaim", mWaitForReclaim);
}
- try {
- Thread.sleep(15000); // timeout to ensure the activity is finished.
- } catch (InterruptedException e) {
+ if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
+ // haven't reached the limit with MAX_INSTANCES, no need to wait for reclaim exception.
+ mWaitForReclaim = false;
}
- stopUsingCodecs();
- // if the test is supposed to wait for reclaim event then this is a failure, otherwise
- // this is a pass.
- finishWithResult(waitForReclaim ? RESULT_CANCELED : RESULT_OK);
+
+ useCodecs();
}
}
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java
index 3e0b111..689babb 100644
--- a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java
+++ b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java
@@ -34,6 +34,7 @@
public static final int TYPE_MIX = 2;
protected String TAG;
+ private static final int FRAME_RATE = 10;
private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
private static final String MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
@@ -64,18 +65,16 @@
private MediaCodec.Callback mCallback = new TestCodecCallback();
- private static MediaFormat getTestFormat(VideoCapabilities vcaps, boolean securePlayback) {
- int maxWidth = vcaps.getSupportedWidths().getUpper();
- int maxHeight = vcaps.getSupportedHeightsFor(maxWidth).getUpper();
- int maxBitrate = vcaps.getBitrateRange().getUpper();
- int maxFramerate = vcaps.getSupportedFrameRatesFor(maxWidth, maxHeight)
- .getUpper().intValue();
+ private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback) {
+ VideoCapabilities vcaps = caps.getVideoCapabilities();
+ int width = vcaps.getSupportedWidths().getLower();
+ int height = vcaps.getSupportedHeightsFor(width).getLower();
+ int bitrate = vcaps.getBitrateRange().getLower();
- MediaFormat format = MediaFormat.createVideoFormat(MIME, maxWidth, maxHeight);
- format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
- CodecCapabilities.COLOR_FormatYUV420Flexible);
- format.setInteger(MediaFormat.KEY_BIT_RATE, maxBitrate);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, maxFramerate);
+ MediaFormat format = MediaFormat.createVideoFormat(MIME, width, height);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, securePlayback);
return format;
@@ -122,23 +121,27 @@
Log.d(TAG, "type is: " + type);
}
- boolean shouldSkip = true;
+ boolean shouldSkip = false;
boolean securePlayback;
if (type == TYPE_NONSECURE || type == TYPE_MIX) {
securePlayback = false;
MediaCodecInfo info = getTestCodecInfo(securePlayback);
if (info != null) {
- shouldSkip = false;
allocateCodecs(max, info, securePlayback);
+ } else {
+ shouldSkip = true;
}
}
- if (type == TYPE_SECURE || type == TYPE_MIX) {
- securePlayback = true;
- MediaCodecInfo info = getTestCodecInfo(securePlayback);
- if (info != null) {
- shouldSkip = false;
- allocateCodecs(max, info, securePlayback);
+ if (!shouldSkip) {
+ if (type == TYPE_SECURE || type == TYPE_MIX) {
+ securePlayback = true;
+ MediaCodecInfo info = getTestCodecInfo(securePlayback);
+ if (info != null) {
+ allocateCodecs(max, info, securePlayback);
+ } else {
+ shouldSkip = true;
+ }
}
}
@@ -154,18 +157,19 @@
protected void allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback) {
String name = info.getName();
CodecCapabilities caps = info.getCapabilitiesForType(MIME);
- VideoCapabilities vcaps = caps.getVideoCapabilities();
- MediaFormat format = getTestFormat(vcaps, securePlayback);
+ MediaFormat format = getTestFormat(caps, securePlayback);
+ MediaCodec codec = null;
for (int i = mCodecs.size(); i < max; ++i) {
try {
Log.d(TAG, "Create codec " + name + " #" + i);
- MediaCodec codec = MediaCodec.createByCodecName(name);
+ codec = MediaCodec.createByCodecName(name);
codec.setCallback(mCallback);
Log.d(TAG, "Configure codec " + format);
codec.configure(format, null, null, 0);
Log.d(TAG, "Start codec " + format);
codec.start();
mCodecs.add(codec);
+ codec = null;
} catch (IllegalArgumentException e) {
Log.d(TAG, "IllegalArgumentException " + e.getMessage());
break;
@@ -175,6 +179,12 @@
} catch (MediaCodec.CodecException e) {
Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode()));
break;
+ } finally {
+ if (codec != null) {
+ Log.d(TAG, "release codec");
+ codec.release();
+ codec = null;
+ }
}
}
}
@@ -184,6 +194,7 @@
Log.d(TAG, "release codec #" + i);
mCodecs.get(i).release();
}
+ mCodecs.clear();
setResult(result);
finish();
Log.d(TAG, "activity finished");
@@ -207,6 +218,7 @@
}
}
+ protected boolean mWaitForReclaim = true;
private Thread mWorkerThread;
private volatile boolean mUseCodecs = true;
private volatile boolean mGotReclaimedException = false;
@@ -214,28 +226,29 @@
mWorkerThread = new Thread(new Runnable() {
@Override
public void run() {
- while (mUseCodecs) {
+ long start = System.currentTimeMillis();
+ long timeSinceStartedMs = 0;
+ while (mUseCodecs && (timeSinceStartedMs < 15000)) { // timeout in 15s
doUseCodecs();
try {
Thread.sleep(50 /* millis */);
} catch (InterruptedException e) {}
+ timeSinceStartedMs = System.currentTimeMillis() - start;
}
if (mGotReclaimedException) {
+ Log.d(TAG, "Got expected reclaim exception.");
finishWithResult(RESULT_OK);
+ } else {
+ Log.d(TAG, "Stopped without getting reclaim exception.");
+ // if the test is supposed to wait for reclaim event then this is a failure,
+ // otherwise this is a pass.
+ finishWithResult(mWaitForReclaim ? RESULT_CANCELED : RESULT_OK);
}
}
});
mWorkerThread.start();
}
- protected void stopUsingCodecs() {
- mUseCodecs = false;
- try {
- mWorkerThread.join(1000);
- } catch (InterruptedException e) {
- }
- }
-
@Override
protected void onDestroy() {
Log.d(TAG, "onDestroy called.");
diff --git a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
index 45fdb13..5937253 100644
--- a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
+++ b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
@@ -32,7 +32,6 @@
import android.util.Log;
import android.view.Surface;
-import com.android.cts.util.ReportLog;
import com.android.cts.util.ResultType;
import com.android.cts.util.ResultUnit;
@@ -52,12 +51,19 @@
private static final String VIDEO_MPEG4 = MediaFormat.MIMETYPE_VIDEO_MPEG4;
private Resources mResources;
- private DeviceReportLog mReportLog = new DeviceReportLog();
+ private DeviceReportLog mReportLog;
@Override
protected void setUp() throws Exception {
super.setUp();
mResources = mContext.getResources();
+ mReportLog = new DeviceReportLog();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mReportLog.deliverReportToHost(getInstrumentation());
+ super.tearDown();
}
private static String[] getDecoderName(String mime) {
@@ -97,6 +103,8 @@
Log.d(TAG, "round #" + i + " decode to buffer");
doDecode(name, video, TOTAL_FRAMES, null);
}
+ // use 0 for summary line, detail for each test config is in the report.
+ mReportLog.printSummary("average fps", 0, ResultType.HIGHER_BETTER, ResultUnit.FPS);
}
}
@@ -222,9 +230,6 @@
String message = "average fps for " + testConfig;
double fps = (double)outputNum / ((finish - start) / 1000.0);
mReportLog.printValue(message, fps, ResultType.HIGHER_BETTER, ResultUnit.FPS);
-
- message = "frame time diff for " + testConfig + ": " + Arrays.toString(frameTimeDiff);
- mReportLog.printValue(message, 0, ResultType.NEUTRAL, ResultUnit.NONE);
}
public void testH264320x240() throws Exception {
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index 15d368f..34baac9 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -22,10 +22,13 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
+import android.net.NetworkRequest;
import android.net.wifi.WifiManager;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -129,6 +132,17 @@
assertTrue(ni.getState() == State.CONNECTED);
}
+ public void testGetActiveNetwork() {
+ Network network = mCm.getActiveNetwork();
+ assertTrue("You must have an active network connection to complete CTS", network != null);
+
+ NetworkInfo ni = mCm.getNetworkInfo(network);
+ assertTrue("Network returned from getActiveNetwork was invalid", ni != null);
+ // Similar to testGetActiveNetworkInfo above.
+ assertTrue(ConnectivityManager.isNetworkTypeValid(ni.getType()));
+ assertTrue(ni.getState() == State.CONNECTED);
+ }
+
public void testGetNetworkInfo() {
for (int type = -1; type <= ConnectivityManager.MAX_NETWORK_TYPE+1; type++) {
if (isSupported(type)) {
@@ -298,6 +312,51 @@
}
}
+ /**
+ * Exercises both registerNetworkCallback and unregisterNetworkCallback. This checks to
+ * see if we get a callback for the TRANSPORT_WIFI transport type being available.
+ *
+ * <p>In order to test that a NetworkCallback occurs, we need some change in the network
+ * state (either a transport or capability is now available). The most straightforward is
+ * WiFi. We could add a version that uses the telephony data connection but it's not clear
+ * that it would increase test coverage by much (how many devices have 3G radio but not Wifi?).
+ */
+ public void testRegisterNetworkCallback() {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ Log.i(TAG, "testRegisterNetworkCallback cannot execute unless devices supports WiFi");
+ return;
+ }
+
+ // We will register for a WIFI network being available or lost.
+ NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+
+ boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
+
+ try {
+ // Make sure WiFi is connected to an access point to start with.
+ if (!previousWifiEnabledState) {
+ connectToWifi();
+ }
+
+ // Now we should expect to get a network callback about availability of the wifi
+ // network even if it was already connected as a state-based action when the callback
+ // is registered.
+ assertTrue("Did not receive NetworkCallback.onAvailable for TRANSPORT_WIFI",
+ callback.waitForAvailable());
+ } catch (InterruptedException e) {
+ fail("Broadcast receiver or NetworkCallback wait was interrupted.");
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+
+ // Return WiFI to its original enabled/disabled state.
+ mWifiManager.setWifiEnabled(previousWifiEnabledState);
+ }
+ }
+
private void connectToWifi() throws InterruptedException {
ConnectivityActionReceiver receiver =
new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI);
@@ -338,4 +397,21 @@
return mReceiveLatch.await(30, TimeUnit.SECONDS);
}
}
+
+ /**
+ * Callback used in testRegisterNetworkCallback that allows caller to block on
+ * {@code onAvailable}.
+ */
+ private class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
+
+ public boolean waitForAvailable() throws InterruptedException {
+ return mAvailableLatch.await(30, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void onAvailable(Network network) {
+ mAvailableLatch.countDown();
+ }
+ }
}
diff --git a/tests/tests/print/src/android/print/cts/BasePrintTest.java b/tests/tests/print/src/android/print/cts/BasePrintTest.java
index 31dfb9f..e75ec94 100644
--- a/tests/tests/print/src/android/print/cts/BasePrintTest.java
+++ b/tests/tests/print/src/android/print/cts/BasePrintTest.java
@@ -60,9 +60,13 @@
import org.mockito.InOrder;
import org.mockito.stubbing.Answer;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeoutException;
@@ -80,6 +84,12 @@
private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
+ private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
+
+ private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
+
+ private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
+
private PrintDocumentActivity mActivity;
private Locale mOldLocale;
@@ -91,11 +101,49 @@
private CallCounter mPrintJobQueuedCallCounter;
private CallCounter mDestroySessionCallCounter;
+ private String[] mEnabledImes;
+
+ private String[] getEnabledImes() throws IOException {
+ List<String> imeList = new ArrayList<>();
+
+ ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+ .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())));
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ imeList.add(line);
+ }
+
+ String[] imeArray = new String[imeList.size()];
+ imeList.toArray(imeArray);
+
+ return imeArray;
+ }
+
+ private void disableImes() throws Exception {
+ mEnabledImes = getEnabledImes();
+ for (String ime : mEnabledImes) {
+ String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
+ SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand);
+ }
+ }
+
+ private void enableImes() throws Exception {
+ for (String ime : mEnabledImes) {
+ String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
+ SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand);
+ }
+ mEnabledImes = null;
+ }
+
@Override
public void setUp() throws Exception {
// Make sure we start with a clean slate.
clearPrintSpoolerData();
enablePrintServices();
+ disableImes();
// Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
// Dexmaker is used by mockito.
@@ -130,6 +178,7 @@
public void tearDown() throws Exception {
// Done with the activity.
getActivity().finish();
+ enableImes();
// Restore the locale if needed.
if (mOldLocale != null) {
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index b1a7130..ed180f6 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -32,8 +32,6 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
- <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
- <uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
@@ -62,6 +60,15 @@
android:resource="@xml/contactprovider_authenticator"/>
</service>
+ <service android:name=".MockInputMethodService"
+ android:label="UserDictionaryInputMethodTestService"
+ android:permission="android.permission.BIND_INPUT_METHOD">
+ <intent-filter>
+ <action android:name="android.view.InputMethod" />
+ </intent-filter>
+ <meta-data android:name="android.view.im"
+ android:resource="@xml/method" />
+ </service>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/provider/AndroidTest.xml b/tests/tests/provider/AndroidTest.xml
new file mode 100644
index 0000000..e3cea41
--- /dev/null
+++ b/tests/tests/provider/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?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
+ -->
+<configuration description="Test module config for provider">
+ <include name="common-config" />
+ <option name="cts-apk-installer:test-file-name" value="CtsProviderTestCases.apk" />
+ <option name="run-command:run-command"
+ value="ime enable com.android.cts.provider/.MockInputMethodService" />
+
+ <option name="run-command:run-command"
+ value="setprop log.tag.CalendarProvider2 VERBOSE" />
+ <option name="run-command:run-command"
+ value="setprop log.tag.CalInstances VERBOSE" />
+ <option name="run-command:run-command"
+ value="setprop log.tag.RecurrenceProcessor VERBOSE" />
+ <option name="run-command:run-command"
+ value="setprop log.tag.CalendarCache VERBOSE" />
+
+ <option name="run-command:teardown-command"
+ value="setprop log.tag.CalendarProvider2 INFO" />
+ <option name="run-command:teardown-command"
+ value="setprop log.tag.CalInstances INFO" />
+ <option name="run-command:teardown-command"
+ value="setprop log.tag.RecurrenceProcessor INFO" />
+ <option name="run-command:teardown-command"
+ value="setprop log.tag.CalendarCache INFO" />
+</configuration>
diff --git a/tests/tests/provider/res/xml/method.xml b/tests/tests/provider/res/xml/method.xml
new file mode 100644
index 0000000..5ac1b80
--- /dev/null
+++ b/tests/tests/provider/res/xml/method.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="android.provider.cts.NoSuchActivity"
+/>
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index c862cc3..8423c40 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -18,6 +18,10 @@
import static android.telecom.cts.TestUtils.*;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -37,6 +41,9 @@
import android.text.TextUtils;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -69,7 +76,7 @@
TelecomManager mTelecomManager;
InCallServiceCallbacks mInCallCallbacks;
String mPreviousDefaultDialer = null;
- MockConnectionService connectionService = new MockConnectionService();
+ MockConnectionService connectionService;
@Override
protected void setUp() throws Exception {
@@ -96,13 +103,16 @@
super.tearDown();
}
- protected PhoneAccount setupConnectionService(CtsConnectionService connectionService,
+ protected PhoneAccount setupConnectionService(MockConnectionService connectionService,
int flags)
throws Exception {
- if (connectionService == null) {
- connectionService = this.connectionService;
+ if (connectionService != null) {
+ this.connectionService = connectionService;
+ } else {
+ // Generate a vanilla mock connection service, if not provided.
+ this.connectionService = new MockConnectionService();
}
- CtsConnectionService.setUp(TEST_PHONE_ACCOUNT, connectionService);
+ CtsConnectionService.setUp(TEST_PHONE_ACCOUNT, this.connectionService);
if ((flags & FLAG_REGISTER) != 0) {
mTelecomManager.registerPhoneAccount(TEST_PHONE_ACCOUNT);
@@ -141,6 +151,21 @@
mInCallCallbacks = new InCallServiceCallbacks() {
@Override
public void onCallAdded(Call call, int numCalls) {
+ Log.i(TAG, "onCallAdded, Call: " + call + "Num Calls: " + numCalls);
+ this.lock.release();
+ }
+ @Override
+ public void onParentChanged(Call call, Call parent) {
+ Log.i(TAG, "onParentChanged, Call: " + call + "Parent: " + parent);
+ }
+ @Override
+ public void onChildrenChanged(Call call, List<Call> children) {
+ Log.i(TAG, "onChildrenChanged, Call: " + call + "Childred: " + children);
+ }
+ @Override
+ public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
+ Log.i(TAG, "onConferenceableCallsChanged, Call: " + call + "Conferenceables: " +
+ conferenceableCalls);
this.lock.release();
}
};
@@ -153,6 +178,11 @@
* {@link CtsConnectionService} which can be tested.
*/
void addAndVerifyNewIncomingCall(Uri incomingHandle, Bundle extras) {
+ int currentCallCount = 0;
+ if (mInCallCallbacks.getService() != null) {
+ currentCallCount = mInCallCallbacks.getService().getCallCount();
+ }
+
if (extras == null) {
extras = new Bundle();
}
@@ -167,7 +197,8 @@
Log.i(TAG, "Test interrupted!");
}
- assertEquals("InCallService should contain 1 call after adding a call.", 1,
+ assertEquals("InCallService should contain 1 more call after adding a call.",
+ currentCallCount + 1,
mInCallCallbacks.getService().getCallCount());
}
@@ -202,6 +233,10 @@
* {@link CtsConnectionService} which can be tested.
*/
void placeAndVerifyCall(Bundle extras, int videoState) {
+ int currentCallCount = 0;
+ if (mInCallCallbacks.getService() != null) {
+ currentCallCount = mInCallCallbacks.getService().getCallCount();
+ }
placeNewCallWithPhoneAccount(extras, videoState);
try {
@@ -212,11 +247,17 @@
Log.i(TAG, "Test interrupted!");
}
- assertEquals("InCallService should contain 1 call after adding a call.", 1,
+ assertEquals("InCallService should contain 1 more call after adding a call.",
+ currentCallCount + 1,
mInCallCallbacks.getService().getCallCount());
}
MockConnection verifyConnectionForOutgoingCall() {
+ // Assuming only 1 connection present
+ return verifyConnectionForOutgoingCall(0);
+ }
+
+ MockConnection verifyConnectionForOutgoingCall(int connectionIndex) {
try {
if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
TimeUnit.MILLISECONDS)) {
@@ -226,19 +267,27 @@
Log.i(TAG, "Test interrupted!");
}
- assertNotNull("Telecom should create outgoing connection for outgoing call",
- connectionService.outgoingConnection);
- assertNull("Telecom should not create incoming connection for outgoing call",
- connectionService.incomingConnection);
+ assertThat("Telecom should create outgoing connection for outgoing call",
+ connectionService.outgoingConnections.size(), not(equalTo(0)));
+ assertEquals("Telecom should not create incoming connections for outgoing calls",
+ 0, connectionService.incomingConnections.size());
+ MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
+ setAndverifyConnectionForOutgoingCall(connection);
+ return connection;
+ }
- connectionService.outgoingConnection.setDialing();
- connectionService.outgoingConnection.setActive();
- assertEquals(Connection.STATE_ACTIVE,
- connectionService.outgoingConnection.getState());
- return connectionService.outgoingConnection;
+ void setAndverifyConnectionForOutgoingCall(MockConnection connection) {
+ connection.setDialing();
+ connection.setActive();
+ assertEquals(Connection.STATE_ACTIVE, connection.getState());
}
MockConnection verifyConnectionForIncomingCall() {
+ // Assuming only 1 connection present
+ return verifyConnectionForIncomingCall(0);
+ }
+
+ MockConnection verifyConnectionForIncomingCall(int connectionIndex) {
try {
if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
TimeUnit.MILLISECONDS)) {
@@ -248,15 +297,42 @@
Log.i(TAG, "Test interrupted!");
}
- assertNull("Telecom should not create outgoing connection for outgoing call",
- connectionService.outgoingConnection);
- assertNotNull("Telecom should create incoming connection for outgoing call",
- connectionService.incomingConnection);
+ assertThat("Telecom should create incoming connections for incoming calls",
+ connectionService.incomingConnections.size(), not(equalTo(0)));
+ assertEquals("Telecom should not create outgoing connections for incoming calls",
+ 0, connectionService.outgoingConnections.size());
+ MockConnection connection = connectionService.incomingConnections.get(connectionIndex);
+ setAndverifyConnectionForIncomingCall(connection);
+ return connection;
+ }
- connectionService.incomingConnection.setRinging();
- assertEquals(Connection.STATE_RINGING,
- connectionService.incomingConnection.getState());
- return connectionService.incomingConnection;
+ void setAndverifyConnectionForIncomingCall(MockConnection connection) {
+ connection.setRinging();
+ assertEquals(Connection.STATE_RINGING, connection.getState());
+ }
+
+ void setAndVerifyConferenceablesForOutgoingConnection(int connectionIndex) {
+ /**
+ * Make all other outgoing connections as conferenceable with this
+ * new connection.
+ */
+ MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
+ List<Connection> confConnections = new ArrayList<>(connectionService.outgoingConnections.size());
+ for (Connection c : connectionService.outgoingConnections) {
+ if (c != connection) {
+ confConnections.add(c);
+ }
+ }
+ connection.setConferenceableConnections(confConnections);
+
+ try {
+ if (!mInCallCallbacks.lock.tryAcquire(3, TimeUnit.SECONDS)) {
+ fail("No call added to the conferenceables list.");
+ }
+ } catch (InterruptedException e) {
+ Log.i(TAG, "Test interrupted!");
+ }
+ assertEquals(connection.getConferenceables(), confConnections);
}
/**
@@ -314,6 +390,23 @@
);
}
+ void assertNumConferenceCalls(final MockInCallService inCallService, final int numCalls) {
+ waitUntilConditionIsTrueOrTimeout(new Condition() {
+ @Override
+ public Object expected() {
+ return numCalls;
+ }
+ @Override
+ public Object actual() {
+ return inCallService.getConferenceCallCount();
+ }
+ },
+ WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+ "InCallService should contain " + numCalls + " conference calls."
+ );
+ }
+
+
void assertMuteState(final InCallService incallService, final boolean isMuted) {
waitUntilConditionIsTrueOrTimeout(
new Condition() {
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConference.java b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
new file mode 100644
index 0000000..9d20fdd
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
@@ -0,0 +1,38 @@
+/*
+ * 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.telecom.cts;
+
+import static android.telecom.CallAudioState.*;
+import android.telecom.CallAudioState;
+import android.telecom.Connection;
+import android.telecom.Conference;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.VideoProfile;
+import android.util.Log;
+
+/**
+ * {@link Conference} subclass that immediately performs any state changes that are a result of
+ * callbacks sent from Telecom.
+ */
+public class MockConference extends Conference {
+
+ // todo: Dummy implementation for now.
+ public MockConference(PhoneAccountHandle phoneAccount) {
+ super(phoneAccount);
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
index 460b060..820d6e8 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
@@ -20,6 +20,7 @@
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
import android.telecom.VideoProfile;
import android.util.Log;
@@ -35,6 +36,7 @@
public int videoState = VideoProfile.STATE_AUDIO_ONLY;
private String mDtmfString = "";
private MockVideoProvider mMockVideoProvider;
+ private PhoneAccountHandle mPhoneAccountHandle;
@Override
public void onAnswer() {
@@ -154,4 +156,13 @@
public MockVideoProvider getMockVideoProvider() {
return mMockVideoProvider;
}
+
+ public void setPhoneAccountHandle(PhoneAccountHandle handle) {
+ mPhoneAccountHandle = handle;
+ }
+
+ public PhoneAccountHandle getPhoneAccountHandle() {
+ return mPhoneAccountHandle;
+ }
+
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
index 250f197..6bc34d7 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
@@ -18,9 +18,12 @@
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
+import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.Semaphore;
/**
@@ -39,14 +42,15 @@
private boolean mCreateVideoProvider = true;
public Semaphore lock = new Semaphore(0);
- public MockConnection outgoingConnection;
- public MockConnection incomingConnection;
+ public List<MockConnection> outgoingConnections = new ArrayList<MockConnection>();
+ public List<MockConnection> incomingConnections = new ArrayList<MockConnection>();
@Override
public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
ConnectionRequest request) {
final MockConnection connection = new MockConnection();
connection.setAddress(request.getAddress(), CONNECTION_PRESENTATION);
+ connection.setPhoneAccountHandle(connectionManagerPhoneAccount);
if (mCreateVideoProvider) {
connection.createMockVideoProvider();
} else {
@@ -54,7 +58,7 @@
}
connection.setVideoState(request.getVideoState());
- outgoingConnection = connection;
+ outgoingConnections.add(connection);
lock.release();
return connection;
}
@@ -67,11 +71,21 @@
connection.createMockVideoProvider();
connection.setVideoState(request.getVideoState());
- incomingConnection = connection;
+ incomingConnections.add(connection);
lock.release();
return connection;
}
+ @Override
+ public void onConference(Connection connection1, Connection connection2) {
+ MockConnection confHost = (MockConnection)connection1;
+ // Create conference and add to telecom
+ MockConference conference = new MockConference(confHost.getPhoneAccountHandle());
+ conference.addConnection(connection1);
+ conference.addConnection(connection2);
+ addConference(conference);
+ }
+
public void setCreateVideoProvider(boolean createVideoProvider) {
mCreateVideoProvider = createVideoProvider;
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
index e725466..921339e 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
@@ -27,6 +27,7 @@
public class MockInCallService extends InCallService {
private ArrayList<Call> mCalls = new ArrayList<>();
+ private ArrayList<Call> mConferenceCalls = new ArrayList<>();
private static InCallServiceCallbacks sCallbacks;
private Map<Call, MockVideoCallCallback> mVideoCallCallbacks =
new ArrayMap<Call, MockVideoCallCallback>();
@@ -40,6 +41,9 @@
public void onCallAdded(Call call, int numCalls) {};
public void onCallRemoved(Call call, int numCalls) {};
public void onCallStateChanged(Call call, int state) {};
+ public void onParentChanged(Call call, Call parent) {};
+ public void onChildrenChanged(Call call, List<Call> children) {};
+ public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {};
final public MockInCallService getService() {
return mService;
@@ -68,6 +72,30 @@
super.onVideoCallChanged(call, videoCall);
saveVideoCall(call, videoCall);
}
+
+ @Override
+ public void onParentChanged(Call call, Call parent) {
+ super.onParentChanged(call, parent);
+ if (getCallbacks() != null) {
+ getCallbacks().onParentChanged(call, parent);
+ }
+ }
+
+ @Override
+ public void onChildrenChanged(Call call, List<Call> children) {
+ super.onChildrenChanged(call, children);
+ if (getCallbacks() != null) {
+ getCallbacks().onChildrenChanged(call, children);
+ }
+ }
+
+ @Override
+ public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
+ super.onConferenceableCallsChanged(call, conferenceableCalls);
+ if (getCallbacks() != null) {
+ getCallbacks().onConferenceableCallsChanged(call, conferenceableCalls);
+ }
+ }
};
private void saveVideoCall(Call call, VideoCall videoCall) {
@@ -93,13 +121,19 @@
@Override
public void onCallAdded(Call call) {
super.onCallAdded(call);
- if (!mCalls.contains(call)) {
- mCalls.add(call);
- call.registerCallback(mCallCallback);
-
- VideoCall videoCall = call.getVideoCall();
- if (videoCall != null) {
- saveVideoCall(call, videoCall);
+ if (call.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE) == true) {
+ if (!mConferenceCalls.contains(call)) {
+ mConferenceCalls.add(call);
+ call.registerCallback(mCallCallback);
+ }
+ } else {
+ if (!mCalls.contains(call)) {
+ mCalls.add(call);
+ call.registerCallback(mCallCallback);
+ VideoCall videoCall = call.getVideoCall();
+ if (videoCall != null) {
+ saveVideoCall(call, videoCall);
+ }
}
}
if (getCallbacks() != null) {
@@ -125,15 +159,32 @@
}
/**
+ * @return the number of conference calls currently added to the {@code InCallService}.
+ */
+ public int getConferenceCallCount() {
+ return mConferenceCalls.size();
+ }
+
+ /**
* @return the most recently added call that exists inside the {@code InCallService}
*/
public Call getLastCall() {
- if (mCalls.size() >= 1) {
+ if (!mCalls.isEmpty()) {
return mCalls.get(mCalls.size() - 1);
}
return null;
}
+ /**
+ * @return the most recently added conference call that exists inside the {@code InCallService}
+ */
+ public Call getLastConferenceCall() {
+ if (!mConferenceCalls.isEmpty()) {
+ return mConferenceCalls.get(mConferenceCalls.size() - 1);
+ }
+ return null;
+ }
+
public void disconnectLastCall() {
final Call call = getLastCall();
if (call != null) {
@@ -141,6 +192,13 @@
}
}
+ public void disconnectLastConferenceCall() {
+ final Call call = getLastConferenceCall();
+ if (call != null) {
+ call.disconnect();
+ }
+ }
+
public static void setCallbacks(InCallServiceCallbacks callbacks) {
synchronized (sLock) {
sCallbacks = callbacks;
diff --git a/tests/tests/telephony/src/android/telephony/cts/CellInfoTest.java b/tests/tests/telephony/src/android/telephony/cts/CellInfoTest.java
index 5b88525..9a93a60 100644
--- a/tests/tests/telephony/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/CellInfoTest.java
@@ -18,7 +18,9 @@
import android.content.Context;
import android.net.ConnectivityManager;
import android.telephony.CellInfo;
-import android.telephony.PhoneStateListener;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoWcdma;
import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -36,6 +38,9 @@
private TelephonyManager mTelephonyManager;
private static ConnectivityManager mCm;
private static final String TAG = "android.telephony.cts.CellInfoTest";
+ // Maximum and minimum possible RSSI values(in dbm).
+ private static final int MAX_RRSI = -10;
+ private static final int MIN_RSSI = -150;
@Override
protected void setUp() throws Exception {
@@ -57,5 +62,66 @@
assertNotNull("TelephonyManager.getAllCellInfo() returned NULL!", allCellInfo);
assertTrue("TelephonyManager.getAllCellInfo() returned zero-length list!",
allCellInfo.size() > 0);
+
+ int numRegisteredCells = 0;
+ for (CellInfo cellInfo : allCellInfo) {
+ if (cellInfo.isRegistered()) {
+ ++numRegisteredCells;
+ }
+ if (cellInfo instanceof CellInfoLte) {
+ verifyLteInfo((CellInfoLte) cellInfo);
+ } else if (cellInfo instanceof CellInfoWcdma) {
+ verifyWcdmaInfo((CellInfoWcdma) cellInfo);
+ } else if (cellInfo instanceof CellInfoGsm) {
+ verifyGsmInfo((CellInfoGsm) cellInfo);
+ }
+ }
+ // At most two cells could be registered.
+ assertTrue("None or too many registered cells : " + numRegisteredCells,
+ numRegisteredCells > 0 && numRegisteredCells <= 2);
+ }
+
+ // Verify lte cell information is within correct range.
+ private void verifyLteInfo(CellInfoLte lte) {
+ verifyRssiDbm(lte.getCellSignalStrength().getDbm());
+ // Verify LTE neighbor information.
+ if (!lte.isRegistered()) {
+ // Only physical cell id is available for LTE neighbor.
+ int pci = lte.getCellIdentity().getPci();
+ // Physical cell id should be within [0, 503].
+ assertTrue("getPci() out of range [0, 503]", pci >= 0 && pci <= 503);
+ }
+ }
+
+ // Verify wcdma cell information is within correct range.
+ private void verifyWcdmaInfo(CellInfoWcdma wcdma) {
+ verifyRssiDbm(wcdma.getCellSignalStrength().getDbm());
+ // Verify wcdma neighbor.
+ if (!wcdma.isRegistered()) {
+ // For wcdma neighbor, only primary scrambling code is available.
+ // Primary scrambling code should be within [0, 511].
+ int psc = wcdma.getCellIdentity().getPsc();
+ assertTrue("getPsc() out of range [0, 511]", psc >= 0 && psc <= 511);
+ }
+ }
+
+ // Verify gsm cell information is within correct range.
+ private void verifyGsmInfo(CellInfoGsm gsm) {
+ verifyRssiDbm(gsm.getCellSignalStrength().getDbm());
+ // Verify gsm neighbor.
+ if (!gsm.isRegistered()) {
+ // lac and cid are available in GSM neighbor information.
+ // Local area code and cellid should be with [0, 65535].
+ int lac = gsm.getCellIdentity().getLac();
+ assertTrue("getLac() out of range [0, 65535]", lac >= 0 && lac <= 65535);
+ int cid = gsm.getCellIdentity().getCid();
+ assertTrue("getCid() out range [0, 65535]", cid >= 0 && cid <= 65535);
+ }
+ }
+
+ // Rssi(in dbm) should be within [MIN_RSSI, MAX_RSSI].
+ private void verifyRssiDbm(int dbm) {
+ assertTrue("getCellSignalStrength().getDbm() out of range",
+ dbm >= MIN_RSSI && dbm <= MAX_RRSI);
}
}
diff --git a/tests/tests/util/src/android/util/cts/ArrayMapTest.java b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
index 7fdd0da..130b354 100644
--- a/tests/tests/util/src/android/util/cts/ArrayMapTest.java
+++ b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
@@ -310,7 +310,7 @@
private static void dump(ArrayMap map1, ArrayMap map2) {
Log.e("test", "ArrayMap of " + map1.size() + " entries:");
- for (int i=0; i<map2.size(); i++) {
+ for (int i=0; i<map1.size(); i++) {
Log.e("test", " " + map1.keyAt(i) + " -> " + map1.valueAt(i));
}
Log.e("test", "ArrayMap of " + map2.size() + " entries:");
diff --git a/tests/tests/util/src/android/util/cts/ArraySetTest.java b/tests/tests/util/src/android/util/cts/ArraySetTest.java
new file mode 100644
index 0000000..112459c
--- /dev/null
+++ b/tests/tests/util/src/android/util/cts/ArraySetTest.java
@@ -0,0 +1,495 @@
+/*
+ * 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.util.cts;
+
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+// As is the case with ArraySet itself, ArraySetTest borrows heavily from ArrayMapTest.
+
+public class ArraySetTest extends AndroidTestCase {
+ private static final String TAG = "ArraySetTest";
+
+ private static final boolean DEBUG = false;
+
+ private static final int OP_ADD = 1;
+ private static final int OP_REM = 2;
+
+ private static int[] OPS = new int[] {
+ OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+ OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+ OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+ OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+ OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+ OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+ OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+ OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+ OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+ OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+ OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+ OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+ OP_ADD, OP_ADD, OP_ADD,
+ OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+ OP_REM, OP_REM, OP_REM,
+ OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+ };
+
+ private static int[] KEYS = new int[] {
+ // General adding and removing.
+ -1, 1900, 600, 200, 1200, 1500, 1800, 100, 1900,
+ 2100, 300, 800, 600, 1100, 1300, 2000, 1000, 1400,
+ 600, -1, 1900, 600, 300, 2100, 200, 800, 800,
+ 1800, 1500, 1300, 1100, 2000, 1400, 1000, 1200, 1900,
+
+ // Shrink when removing item from end.
+ 100, 200, 300, 400, 500, 600, 700, 800, 900,
+ 900, 800, 700, 600, 500, 400, 300, 200, 100,
+
+ // Shrink when removing item from middle.
+ 100, 200, 300, 400, 500, 600, 700, 800, 900,
+ 900, 800, 700, 600, 500, 400, 200, 300, 100,
+
+ // Shrink when removing item from front.
+ 100, 200, 300, 400, 500, 600, 700, 800, 900,
+ 900, 800, 700, 600, 500, 400, 100, 200, 300,
+
+ // Test hash collisions.
+ 105, 106, 108, 104, 102, 102, 107, 5, 205,
+ 4, 202, 203, 3, 5, 101, 109, 200, 201,
+ 0, -1, 100,
+ 106, 108, 104, 102, 103, 105, 107, 101, 109,
+ -1, 100, 0,
+ 4, 5, 3, 5, 200, 203, 202, 201, 205,
+ };
+
+ public static class ControlledHash {
+ final int mValue;
+
+ ControlledHash(int value) {
+ mValue = value;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+ return mValue == ((ControlledHash)o).mValue;
+ }
+
+ @Override
+ public final int hashCode() {
+ return mValue/100;
+ }
+
+ @Override
+ public final String toString() {
+ return Integer.toString(mValue);
+ }
+ }
+
+ private static boolean compare(Object v1, Object v2) {
+ if (v1 == null) {
+ return v2 == null;
+ }
+ if (v2 == null) {
+ return false;
+ }
+ return v1.equals(v2);
+ }
+
+ private static <E> void compareSets(HashSet<E> set, ArraySet<E> array) {
+ assertEquals("Bad size", set.size(), array.size());
+
+ // Check that every entry in HashSet is in ArraySet.
+ for (E entry : set) {
+ assertTrue("ArraySet missing value: " + entry, array.contains(entry));
+ }
+
+ // Check that every entry in ArraySet is in HashSet using ArraySet.iterator().
+ for (E entry : array) {
+ assertTrue("ArraySet (via iterator) has unexpected value: " + entry,
+ set.contains(entry));
+ }
+
+ // Check that every entry in ArraySet is in HashSet using ArraySet.valueAt().
+ for (int i = 0; i < array.size(); ++i) {
+ E entry = array.valueAt(i);
+ assertTrue("ArraySet (via valueAt) has unexpected value: " + entry,
+ set.contains(entry));
+ }
+
+ if (set.hashCode() != array.hashCode()) {
+ assertEquals("Set hash codes differ", set.hashCode(), array.hashCode());
+ }
+
+ assertTrue("HashSet.equals(ArraySet) failed", set.equals(array));
+ assertTrue("ArraySet.equals(HashSet) failed", array.equals(set));
+
+ assertTrue("HashSet.containsAll(ArraySet) failed", set.containsAll(array));
+ assertTrue("ArraySet.containsAll(HashSet) failed", array.containsAll(set));
+ }
+
+ private static <E> void compareArraySetAndRawArray(ArraySet<E> arraySet, Object[] rawArray) {
+ assertEquals("Bad size", arraySet.size(), rawArray.length);
+ for (int i = 0; i < rawArray.length; ++i) {
+ assertEquals("ArraySet<E> and raw array unequal at index " + i,
+ arraySet.valueAt(i), rawArray[i]);
+ }
+ }
+
+ private static <E> void validateArraySet(ArraySet<E> array) {
+ int index = 0;
+ Iterator<E> iter = array.iterator();
+ while (iter.hasNext()) {
+ E value = iter.next();
+ E realValue = array.valueAt(index);
+ if (!compare(realValue, value)) {
+ fail("Bad array set entry: expected " + realValue
+ + ", got " + value + " at index " + index);
+ }
+ index++;
+ }
+
+ assertEquals("Length of iteration was unequal to size()", array.size(), index);
+ }
+
+ private static <E> void dump(HashSet<E> set, ArraySet<E> array) {
+ Log.e(TAG, "HashSet of " + set.size() + " entries:");
+ for (E entry : set) {
+ Log.e(TAG, " " + entry);
+ }
+ Log.e(TAG, "ArraySet of " + array.size() + " entries:");
+ for (int i = 0; i < array.size(); i++) {
+ Log.e(TAG, " " + array.valueAt(i));
+ }
+ }
+
+ private static void dump(ArraySet set1, ArraySet set2) {
+ Log.e(TAG, "ArraySet of " + set1.size() + " entries:");
+ for (int i = 0; i < set1.size(); i++) {
+ Log.e(TAG, " " + set1.valueAt(i));
+ }
+ Log.e(TAG, "ArraySet of " + set2.size() + " entries:");
+ for (int i = 0; i < set2.size(); i++) {
+ Log.e(TAG, " " + set2.valueAt(i));
+ }
+ }
+
+ public void testTest() {
+ assertEquals("OPS and KEYS must be equal length", OPS.length, KEYS.length);
+ }
+
+ public void testBasicArraySet() {
+ HashSet<ControlledHash> hashSet = new HashSet<ControlledHash>();
+ ArraySet<ControlledHash> arraySet = new ArraySet<ControlledHash>();
+
+ for (int i = 0; i < OPS.length; i++) {
+ ControlledHash key = KEYS[i] < 0 ? null : new ControlledHash(KEYS[i]);
+ String strKey = KEYS[i] < 0 ? null : Integer.toString(KEYS[i]);
+ switch (OPS[i]) {
+ case OP_ADD:
+ if (DEBUG) Log.i(TAG, "Adding key: " + key);
+ boolean hashAdded = hashSet.add(key);
+ boolean arrayAdded = arraySet.add(key);
+ assertEquals("Adding key " + key + " was not symmetric in HashSet and "
+ + "ArraySet", hashAdded, arrayAdded);
+ break;
+ case OP_REM:
+ if (DEBUG) Log.i(TAG, "Removing key: " + key);
+ boolean hashRemoved = hashSet.remove(key);
+ boolean arrayRemoved = arraySet.remove(key);
+ assertEquals("Removing key " + key + " was not symmetric in HashSet and "
+ + "ArraySet", hashRemoved, arrayRemoved);
+ break;
+ default:
+ fail("Bad operation " + OPS[i] + " @ " + i);
+ return;
+ }
+ if (DEBUG) dump(hashSet, arraySet);
+
+ try {
+ validateArraySet(arraySet);
+ } catch (Throwable e) {
+ Log.e(TAG, e.getMessage());
+ dump(hashSet, arraySet);
+ throw e;
+ }
+
+ try {
+ compareSets(hashSet, arraySet);
+ } catch (Throwable e) {
+ Log.e(TAG, e.getMessage());
+ dump(hashSet, arraySet);
+ throw e;
+ }
+ }
+
+ // Check to see if HashSet.iterator().remove() works as expected.
+ arraySet.add(new ControlledHash(50000));
+ ControlledHash lookup = new ControlledHash(50000);
+ Iterator<ControlledHash> it = arraySet.iterator();
+ while (it.hasNext()) {
+ if (it.next().equals(lookup)) {
+ it.remove();
+ }
+ }
+ if (arraySet.contains(lookup)) {
+ String msg = "Bad ArraySet iterator: didn't remove test key";
+ Log.e(TAG, msg);
+ dump(hashSet, arraySet);
+ fail(msg);
+ }
+
+ Log.e(TAG, "Test successful; printing final map.");
+ dump(hashSet, arraySet);
+ }
+
+ public void testCopyArraySet() {
+ // set copy constructor test
+ ArraySet newSet = new ArraySet<Integer>();
+ for (int i = 0; i < 10; ++i) {
+ newSet.add(i);
+ }
+
+ ArraySet copySet = new ArraySet(newSet);
+ if (!compare(copySet, newSet)) {
+ String msg = "ArraySet copy constructor failure: expected " +
+ newSet + ", got " + copySet;
+ Log.e(TAG, msg);
+ dump(newSet, copySet);
+ fail(msg);
+ return;
+ }
+ }
+
+ public void testEqualsArrayMap() {
+ ArraySet<Integer> set1 = new ArraySet<Integer>();
+ ArraySet<Integer> set2 = new ArraySet<Integer>();
+ HashSet<Integer> set3 = new HashSet<Integer>();
+ if (!compare(set1, set2) || !compare(set1, set3) || !compare(set3, set2)) {
+ fail("ArraySet equals failure for empty sets " + set1 + ", " +
+ set2 + ", " + set3);
+ }
+
+ for (int i = 0; i < 10; ++i) {
+ set1.add(i);
+ set2.add(i);
+ set3.add(i);
+ }
+ if (!compare(set1, set2) || !compare(set1, set3) || !compare(set3, set2)) {
+ fail("ArraySet equals failure for populated sets " + set1 + ", " +
+ set2 + ", " + set3);
+ }
+
+ set1.remove(0);
+ if (compare(set1, set2) || compare(set1, set3) || compare(set3, set1)) {
+ fail("ArraySet equals failure for set size " + set1 + ", " +
+ set2 + ", " + set3);
+ }
+ }
+
+ public void testIsEmpty() {
+ ArraySet<Integer> set = new ArraySet<Integer>();
+ assertEquals("New ArraySet should have size==0", 0, set.size());
+ assertTrue("New ArraySet should be isEmptry", set.isEmpty());
+
+ set.add(3);
+ assertEquals("ArraySet has incorrect size", 1, set.size());
+ assertFalse("ArraySet should not be isEmptry", set.isEmpty());
+
+ set.remove(3);
+ assertEquals("ArraySet should have size==0", 0, set.size());
+ assertTrue("ArraySet should be isEmptry", set.isEmpty());
+ }
+
+ public void testRemoveAt() {
+ ArraySet<Integer> set = new ArraySet<Integer>();
+
+ for (int i = 0; i < 10; ++i) {
+ set.add(i * 10);
+ }
+
+ int indexToDelete = 6;
+ assertEquals(10, set.size());
+ assertEquals(indexToDelete * 10, set.valueAt(indexToDelete).intValue());
+ assertEquals(indexToDelete * 10, set.removeAt(indexToDelete).intValue());
+ assertEquals(9, set.size());
+
+ for (int i = 0; i < 9; ++i) {
+ int expectedValue = ((i >= indexToDelete) ? (i + 1) : i) * 10;
+ assertEquals(expectedValue, set.valueAt(i).intValue());
+ }
+
+ for (int i = 9; i > 0; --i) {
+ set.removeAt(0);
+ assertEquals(i - 1, set.size());
+ }
+
+ assertTrue(set.isEmpty());
+
+ try {
+ set.removeAt(0);
+ fail("Expected ArrayIndexOutOfBoundsException");
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ public void testIndexOf() {
+ ArraySet<Integer> set = new ArraySet<Integer>();
+
+ for (int i = 0; i < 10; ++i) {
+ set.add(i * 10);
+ }
+
+ for (int i = 0; i < 10; ++i) {
+ assertEquals("indexOf(" + (i * 10) + ")", i, set.indexOf(i * 10));
+ }
+ }
+
+ public void testAddAll() {
+ ArraySet<Integer> arraySet = new ArraySet<Integer>();
+ ArraySet<Integer> testArraySet = new ArraySet<Integer>();
+ ArrayList<Integer> testArrayList = new ArrayList<Integer>();
+
+ for (int i = 0; i < 10; ++i) {
+ testArraySet.add(i * 10);
+ testArrayList.add(i * 10);
+ }
+
+ assertTrue(arraySet.isEmpty());
+
+ // addAll(ArraySet) has no return value.
+ arraySet.addAll(testArraySet);
+ assertTrue("ArraySet.addAll(ArraySet) failed", arraySet.containsAll(testArraySet));
+
+ arraySet.clear();
+ assertTrue(arraySet.isEmpty());
+
+ // addAll(Collection) returns true if any items were added.
+ assertTrue(arraySet.addAll(testArrayList));
+ assertTrue("ArraySet.addAll(Container) failed", arraySet.containsAll(testArrayList));
+ assertTrue("ArraySet.addAll(Container) failed", arraySet.containsAll(testArraySet));
+ // Adding the same Collection should return false.
+ assertFalse(arraySet.addAll(testArrayList));
+ assertTrue("ArraySet.addAll(Container) failed", arraySet.containsAll(testArrayList));
+ }
+
+ public void testRemoveAll() {
+ ArraySet<Integer> arraySet = new ArraySet<Integer>();
+ ArraySet<Integer> arraySetToRemove = new ArraySet<Integer>();
+ ArrayList<Integer> arrayListToRemove = new ArrayList<Integer>();
+
+ for (int i = 0; i < 10; ++i) {
+ arraySet.add(i * 10);
+ }
+
+ for (int i = 6; i < 15; ++i) {
+ arraySetToRemove.add(i * 10);
+ }
+
+ for (int i = 3; i > -3; --i) {
+ arrayListToRemove.add(i * 10);
+ }
+
+ assertEquals(10, arraySet.size());
+
+ // Remove [6,14] (really [6,9]) via another ArraySet.
+ assertTrue(arraySet.removeAll(arraySetToRemove));
+ assertEquals(6, arraySet.size());
+ assertFalse(arraySet.removeAll(arraySetToRemove));
+ assertEquals(6, arraySet.size());
+
+ // Remove [-2,3] (really [0,3]) via an ArrayList (ie Collection).
+ assertTrue(arraySet.removeAll(arrayListToRemove));
+ assertEquals(2, arraySet.size());
+ assertFalse(arraySet.removeAll(arrayListToRemove));
+ assertEquals(2, arraySet.size());
+
+ // Remove the rest of the items.
+ ArraySet<Integer> copy = new ArraySet<Integer>(arraySet);
+ assertTrue(arraySet.removeAll(copy));
+ assertEquals(0, arraySet.size());
+ assertFalse(arraySet.removeAll(copy));
+ assertEquals(0, arraySet.size());
+ }
+
+ public void testRetainAll() {
+ ArraySet<Integer> arraySet = new ArraySet<Integer>();
+ ArrayList<Integer> arrayListToRetain = new ArrayList<Integer>();
+
+ for (int i = 0; i < 10; ++i) {
+ arraySet.add(i * 10);
+ }
+
+ arrayListToRetain.add(30);
+ arrayListToRetain.add(50);
+ arrayListToRetain.add(51); // bogus value
+
+ assertEquals(10, arraySet.size());
+
+ assertTrue(arraySet.retainAll(arrayListToRetain));
+ assertEquals(2, arraySet.size());
+
+ assertTrue(arraySet.contains(30));
+ assertTrue(arraySet.contains(50));
+ assertFalse(arraySet.contains(51));
+
+ // Nothing should change.
+ assertFalse(arraySet.retainAll(arrayListToRetain));
+ assertEquals(2, arraySet.size());
+ }
+
+ public void testToArray() {
+ ArraySet<Integer> arraySet = new ArraySet<Integer>();
+ for (int i = 0; i < 10; ++i) {
+ arraySet.add(i * 10);
+ }
+
+ // Allocate a new array with the right type given a zero-length ephemeral array.
+ Integer[] copiedArray = arraySet.toArray(new Integer[0]);
+ compareArraySetAndRawArray(arraySet, copiedArray);
+
+ // Allocate a new array with the right type given an undersized array.
+ Integer[] undersizedArray = new Integer[5];
+ copiedArray = arraySet.toArray(undersizedArray);
+ compareArraySetAndRawArray(arraySet, copiedArray);
+ assertNotSame(undersizedArray, copiedArray);
+
+ // Use the passed array that is large enough to hold the ArraySet.
+ Integer[] rightSizedArray = new Integer[10];
+ copiedArray = arraySet.toArray(rightSizedArray);
+ compareArraySetAndRawArray(arraySet, copiedArray);
+ assertSame(rightSizedArray, copiedArray);
+
+ // Create a new Object[] array.
+ Object[] objectArray = arraySet.toArray();
+ compareArraySetAndRawArray(arraySet, objectArray);
+ }
+}
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 65e95d5..b7e0076 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -92,7 +92,7 @@
</activity>
<activity android:name="android.view.animation.cts.GridLayoutAnimCtsActivity"
- android:label="GridLayoutAnimCtsActivity">
+ android:label="GridLayoutAnimCtsActivity" android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
@@ -100,7 +100,7 @@
</activity>
<activity android:name="android.view.animation.cts.LayoutAnimCtsActivity"
- android:label="LayoutAnimCtsActivity">
+ android:label="LayoutAnimCtsActivity" android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
diff --git a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
index 4ef0c03..ff3bcfd 100644
--- a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
+++ b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
@@ -34,6 +34,7 @@
PICKOPTION_REQUEST_CANCEL_TEST,
COMMANDREQUEST_TEST,
COMMANDREQUEST_CANCEL_TEST,
+ SUPPORTS_COMMANDS_TEST,
}
public static final String TESTCASE_TYPE = "testcase_type";
public static final String TESTINFO = "testinfo";
@@ -51,6 +52,7 @@
public static final String ABORT_REQUEST_SUCCESS = "abort ok";
public static final String PICKOPTION_REQUEST_SUCCESS = "pickoption ok";
public static final String COMMANDREQUEST_SUCCESS = "commandrequest ok";
+ public static final String SUPPORTS_COMMANDS_SUCCESS = "supportsCommands ok";
public static final String CONFIRMATION_REQUEST_CANCEL_SUCCESS = "confirm cancel ok";
public static final String COMPLETION_REQUEST_CANCEL_SUCCESS = "completion canel ok";
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
index a5068e0..eeb4047 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -84,7 +84,7 @@
Log.i(TAG, "in onGetSupportedCommands");
for (int idx = 0; idx < commands.length; idx++) {
results[idx] = Utils.TEST_COMMAND.equals(commands[idx]);
- Log.i(TAG, "command is " + commands[idx]);
+ Log.i(TAG, "command " + commands[idx] + ", support = " + results[idx]);
}
return results;
}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
index 2badb27..0fa89e1 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
@@ -91,7 +91,7 @@
verifySingleTestcaseResult(t, singleResult);
}
}
- assertEquals(2, numFails);
+ assertEquals(0, numFails);
mTestActivity.finish();
}
@@ -128,6 +128,9 @@
case PICKOPTION_REQUEST_TEST:
assertTrue(result.equals(Utils.PICKOPTION_REQUEST_SUCCESS));
break;
+ case SUPPORTS_COMMANDS_TEST:
+ assertTrue(result.equals(Utils.SUPPORTS_COMMANDS_SUCCESS));
+ break;
default:
Log.wtf(TAG, "not expected");
break;
diff --git a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
index 47ec223..1ef6a5c 100644
--- a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
+++ b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
@@ -92,6 +92,19 @@
case COMMANDREQUEST_TEST:
case COMMANDREQUEST_CANCEL_TEST:
+ commandRequest();
+ break;
+
+ case SUPPORTS_COMMANDS_TEST:
+ String[] commands = {Utils.TEST_COMMAND};
+ boolean[] supported = mInteractor.supportsCommands(commands);
+ Log.i(TAG, "from supportsCommands: " + supported);
+ if (supported.length == 1 && supported[0]) {
+ addTestResult(Utils.SUPPORTS_COMMANDS_SUCCESS);
+ } else {
+ addTestResult(Utils.TEST_ERROR + " supported commands failure!");
+ }
+ saveTestResults();
continueTests();
break;
}
@@ -234,10 +247,6 @@
}
private void commandRequest() {
- // uncomment these lines once CommandRequest is working. b/22124996
-// Log.i(TAG, "from supportsCommands: " + mInteractor.supportsCommands(commands));
-// String[] commands = {Utils.TEST_COMMAND};
-
CommandRequest req = new VoiceInteractor.CommandRequest(Utils.TEST_COMMAND, mTestinfo) {
@Override
public void onCancel() {
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
index 31e1f8d..f5abd5e 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
@@ -34,10 +34,25 @@
private final List<ApiMethod> mApiMethods = new ArrayList<ApiMethod>();
- ApiClass(String name, boolean deprecated, boolean classAbstract) {
+ private final String mSuperClassName;
+
+ private ApiClass mSuperClass;
+
+ /**
+ * @param name The name of the class
+ * @param deprecated true iff the class is marked as deprecated
+ * @param classAbstract true iff the class is abstract
+ * @param superClassName The fully qualified name of the super class
+ */
+ ApiClass(
+ String name,
+ boolean deprecated,
+ boolean classAbstract,
+ String superClassName) {
mName = name;
mDeprecated = deprecated;
mAbstract = classAbstract;
+ mSuperClassName = superClassName;
}
@Override
@@ -54,22 +69,20 @@
return mDeprecated;
}
+ public String getSuperClassName() {
+ return mSuperClassName;
+ }
+
public boolean isAbstract() {
return mAbstract;
}
+ public void setSuperClass(ApiClass superClass) { mSuperClass = superClass; }
+
public void addConstructor(ApiConstructor constructor) {
mApiConstructors.add(constructor);
}
- public ApiConstructor getConstructor(List<String> parameterTypes) {
- for (ApiConstructor constructor : mApiConstructors) {
- if (parameterTypes.equals(constructor.getParameterTypes())) {
- return constructor;
- }
- }
- return null;
- }
public Collection<ApiConstructor> getConstructors() {
return Collections.unmodifiableList(mApiConstructors);
@@ -79,15 +92,29 @@
mApiMethods.add(method);
}
- public ApiMethod getMethod(String name, List<String> parameterTypes, String returnType) {
- for (ApiMethod method : mApiMethods) {
- if (name.equals(method.getName())
- && parameterTypes.equals(method.getParameterTypes())
- && returnType.equals(method.getReturnType())) {
- return method;
- }
+ /** Look for a matching constructor and mark it as covered */
+ public void markConstructorCovered(List<String> parameterTypes) {
+ if (mSuperClass != null) {
+ // Mark matching constructors in the superclass
+ mSuperClass.markConstructorCovered(parameterTypes);
}
- return null;
+ ApiConstructor apiConstructor = getConstructor(parameterTypes);
+ if (apiConstructor != null) {
+ apiConstructor.setCovered(true);
+ }
+
+ }
+
+ /** Look for a matching method and if found and mark it as covered */
+ public void markMethodCovered(String name, List<String> parameterTypes, String returnType) {
+ if (mSuperClass != null) {
+ // Mark matching methods in the super class
+ mSuperClass.markMethodCovered(name, parameterTypes, returnType);
+ }
+ ApiMethod apiMethod = getMethod(name, parameterTypes, returnType);
+ if (apiMethod != null) {
+ apiMethod.setCovered(true);
+ }
}
public Collection<ApiMethod> getMethods() {
@@ -126,4 +153,24 @@
public int getMemberSize() {
return getTotalMethods();
}
+
+ private ApiMethod getMethod(String name, List<String> parameterTypes, String returnType) {
+ for (ApiMethod method : mApiMethods) {
+ if (name.equals(method.getName())
+ && parameterTypes.equals(method.getParameterTypes())
+ && returnType.equals(method.getReturnType())) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+ private ApiConstructor getConstructor(List<String> parameterTypes) {
+ for (ApiConstructor constructor : mApiConstructors) {
+ if (parameterTypes.equals(constructor.getParameterTypes())) {
+ return constructor;
+ }
+ }
+ return null;
+ }
}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
index adf2ea9..953aab3 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
@@ -39,10 +39,11 @@
return Collections.unmodifiableCollection(mPackages.values());
}
- public void removeEmptyAbstractClasses() {
+ /** Iterate through all packages and update all classes to include its superclass */
+ public void resolveSuperClasses() {
for (Map.Entry<String, ApiPackage> entry : mPackages.entrySet()) {
ApiPackage pkg = entry.getValue();
- pkg.removeEmptyAbstractClasses();
+ pkg.resolveSuperClasses(mPackages);
}
}
}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
index 053cd12..582c2b6 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
@@ -29,15 +29,35 @@
private final String mReturnType;
- private boolean mDeprecated;
+ private final boolean mDeprecated;
+
+ private final String mVisibility;
+
+ private final boolean mStaticMethod;
+
+ private final boolean mFinalMethod;
+
+ private final boolean mAbstractMethod;
private boolean mIsCovered;
- ApiMethod(String name, List<String> parameterTypes, String returnType, boolean deprecated) {
+ ApiMethod(
+ String name,
+ List<String> parameterTypes,
+ String returnType,
+ boolean deprecated,
+ String visibility,
+ boolean staticMethod,
+ boolean finalMethod,
+ boolean abstractMethod) {
mName = name;
mParameterTypes = new ArrayList<String>(parameterTypes);
mReturnType = returnType;
mDeprecated = deprecated;
+ mVisibility = visibility;
+ mStaticMethod = staticMethod;
+ mFinalMethod = finalMethod;
+ mAbstractMethod = abstractMethod;
}
@Override
@@ -65,6 +85,14 @@
return mIsCovered;
}
+ public String getVisibility() { return mVisibility; }
+
+ public boolean isAbstractMethod() { return mAbstractMethod; }
+
+ public boolean isStaticMethod() { return mStaticMethod; }
+
+ public boolean isFinalMethod() { return mFinalMethod; }
+
public void setCovered(boolean covered) {
mIsCovered = covered;
}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
index e0bf73f..7be7e3c 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
@@ -77,14 +77,26 @@
return getTotalMethods();
}
- public void removeEmptyAbstractClasses() {
+ /** Iterate through all classes and add superclass. */
+ public void resolveSuperClasses(Map<String, ApiPackage> packageMap) {
Iterator<Entry<String, ApiClass>> it = mApiClassMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, ApiClass> entry = it.next();
- ApiClass cls = entry.getValue();
- if (cls.isAbstract() && (cls.getTotalMethods() == 0)) {
- // this is essentially interface
- it.remove();
+ ApiClass apiClass = entry.getValue();
+ if (apiClass.getSuperClassName() != null) {
+ String superClassName = apiClass.getSuperClassName();
+ // Split the fully qualified class name into package and class name.
+ String packageName = superClassName.substring(0, superClassName.lastIndexOf('.'));
+ String className = superClassName.substring(
+ superClassName.lastIndexOf('.') + 1, superClassName.length());
+ if (packageMap.containsKey(packageName)) {
+ ApiPackage apiPackage = packageMap.get(packageName);
+ ApiClass superClass = apiPackage.getClass(className);
+ if (superClass != null) {
+ // Add the super class
+ apiClass.setSuperClass(superClass);
+ }
+ }
}
}
}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
index 05cb4e1..3f2f353 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -117,7 +117,8 @@
*/
ApiCoverage apiCoverage = getEmptyApiCoverage(apiXmlPath);
- apiCoverage.removeEmptyAbstractClasses();
+ // Add superclass information into api coverage.
+ apiCoverage.resolveSuperClasses();
for (File testApk : testApks) {
addApiCoverage(apiCoverage, testApk, dexDeps);
}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
index b9f9e9c..de9f5d5 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
@@ -40,6 +40,12 @@
private boolean mCurrentMethodIsAbstract;
+ private String mCurrentMethodVisibility;
+
+ private boolean mCurrentMethodStaticMethod;
+
+ private boolean mCurrentMethodFinalMethod;
+
private boolean mDeprecated;
@@ -69,7 +75,9 @@
mIgnoreCurrentClass = false;
mCurrentClassName = getValue(attributes, "name");
mDeprecated = isDeprecated(attributes);
- ApiClass apiClass = new ApiClass(mCurrentClassName, mDeprecated, isAbstract(attributes));
+ String superClass = attributes.getValue("extends");
+ ApiClass apiClass = new ApiClass(
+ mCurrentClassName, mDeprecated, is(attributes, "abstract"), superClass);
ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName);
apiPackage.addClass(apiClass);
} else if ("interface".equalsIgnoreCase(localName)) {
@@ -82,7 +90,10 @@
mDeprecated = isDeprecated(attributes);
mCurrentMethodName = getValue(attributes, "name");
mCurrentMethodReturnType = getValue(attributes, "return");
- mCurrentMethodIsAbstract = isAbstract(attributes);
+ mCurrentMethodIsAbstract = is(attributes, "abstract");
+ mCurrentMethodVisibility = getValue(attributes, "visibility");
+ mCurrentMethodStaticMethod = is(attributes, "static");
+ mCurrentMethodFinalMethod = is(attributes, "final");
mCurrentParameterTypes.clear();
} else if ("parameter".equalsIgnoreCase(localName)) {
mCurrentParameterTypes.add(getValue(attributes, "type"));
@@ -107,11 +118,15 @@
ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
apiClass.addConstructor(apiConstructor);
} else if ("method".equalsIgnoreCase(localName)) {
- if (mCurrentMethodIsAbstract) { // do not add abstract method
- return;
- }
- ApiMethod apiMethod = new ApiMethod(mCurrentMethodName, mCurrentParameterTypes,
- mCurrentMethodReturnType, mDeprecated);
+ ApiMethod apiMethod = new ApiMethod(
+ mCurrentMethodName,
+ mCurrentParameterTypes,
+ mCurrentMethodReturnType,
+ mDeprecated,
+ mCurrentMethodVisibility,
+ mCurrentMethodStaticMethod,
+ mCurrentMethodFinalMethod,
+ mCurrentMethodIsAbstract);
ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName);
ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
apiClass.addMethod(apiMethod);
@@ -129,8 +144,8 @@
return "deprecated".equals(attributes.getValue("deprecated"));
}
- private boolean isAbstract(Attributes attributes) {
- return "true".equals(attributes.getValue("abstract"));
+ private static boolean is(Attributes attributes, String valueName) {
+ return "true".equals(attributes.getValue(valueName));
}
private boolean isEnum(Attributes attributes) {
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
index 0a90bdd..3df532e 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
@@ -73,10 +73,7 @@
if (apiPackage != null) {
ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
if (apiClass != null) {
- ApiConstructor apiConstructor = apiClass.getConstructor(mCurrentParameterTypes);
- if (apiConstructor != null) {
- apiConstructor.setCovered(true);
- }
+ apiClass.markConstructorCovered(mCurrentParameterTypes);
}
}
} else if ("method".equalsIgnoreCase(localName)) {
@@ -84,11 +81,8 @@
if (apiPackage != null) {
ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
if (apiClass != null) {
- ApiMethod apiMethod = apiClass.getMethod(mCurrentMethodName,
- mCurrentParameterTypes, mCurrentMethodReturnType);
- if (apiMethod != null) {
- apiMethod.setCovered(true);
- }
+ apiClass.markMethodCovered(
+ mCurrentMethodName, mCurrentParameterTypes, mCurrentMethodReturnType);
}
}
}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
index e3e2e7c..3adc020 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
@@ -103,7 +103,18 @@
private static void printMethod(ApiMethod method, PrintStream out) {
StringBuilder builder = new StringBuilder(" [")
.append(method.isCovered() ? "X" : " ")
- .append("] ").append(method.getReturnType()).append(" ")
+ .append("] ")
+ .append(method.getVisibility()).append(" ");
+ if (method.isAbstractMethod()) {
+ builder.append("abstract ");
+ }
+ if (method.isStaticMethod()) {
+ builder.append("static ");
+ }
+ if (method.isFinalMethod()) {
+ builder.append("final ");
+ }
+ builder.append(method.getReturnType()).append(" ")
.append(method.getName()).append("(");
List<String> parameterTypes = method.getParameterTypes();
int numParameterTypes = parameterTypes.size();
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
index 570b316..4310d20 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
@@ -66,7 +66,7 @@
+ "\" numCovered=\"" + pkgTotalCovered
+ "\" numTotal=\"" + pkgTotal
+ "\" coveragePercentage=\""
- + Math.round(pkg.getCoveragePercentage())
+ + Math.round(pkg.getCoveragePercentage())
+ "\">");
List<ApiClass> classes = new ArrayList<ApiClass>(pkg.getClasses());
@@ -103,6 +103,10 @@
out.println("<method name=\"" + method.getName()
+ "\" returnType=\"" + method.getReturnType()
+ "\" deprecated=\"" + method.isDeprecated()
+ + "\" static=\"" + method.isStaticMethod()
+ + "\" final=\"" + method.isFinalMethod()
+ + "\" visibility=\"" + method.getVisibility()
+ + "\" abstract=\"" + method.isAbstractMethod()
+ "\" covered=\"" + method.isCovered() + "\">");
if (method.isDeprecated()) {
if (method.isCovered()) {
diff --git a/tools/cts-api-coverage/src/res/api-coverage.xsl b/tools/cts-api-coverage/src/res/api-coverage.xsl
index b11a8c4..1a56eb0 100644
--- a/tools/cts-api-coverage/src/res/api-coverage.xsl
+++ b/tools/cts-api-coverage/src/res/api-coverage.xsl
@@ -101,14 +101,17 @@
<xsl:for-each select="api-coverage/api/package">
<xsl:call-template name="packageOrClassListItem">
<xsl:with-param name="bulletClass" select="'package'" />
+ <xsl:with-param name="toggleId" select="@name" />
</xsl:call-template>
<div class="packageDetails" id="{@name}" style="display: none">
<ul>
<xsl:for-each select="class">
+ <xsl:variable name="packageClassId" select="concat(../@name, '.', @name)"/>
<xsl:call-template name="packageOrClassListItem">
<xsl:with-param name="bulletClass" select="'class'" />
+ <xsl:with-param name="toggleId" select="$packageClassId" />
</xsl:call-template>
- <div class="classDetails" id="{@name}" style="display: none">
+ <div class="classDetails" id="{$packageClassId}" style="display: none">
<xsl:for-each select="constructor">
<xsl:call-template name="methodListItem" />
</xsl:for-each>
@@ -124,9 +127,10 @@
</body>
</html>
</xsl:template>
-
+
<xsl:template name="packageOrClassListItem">
<xsl:param name="bulletClass" />
+ <xsl:param name="toggleId"/>
<xsl:variable name="colorClass">
<xsl:choose>
@@ -135,7 +139,7 @@
<xsl:otherwise>green</xsl:otherwise>
</xsl:choose>
</xsl:variable>
-
+
<xsl:variable name="deprecatedClass">
<xsl:choose>
<xsl:when test="@deprecated = 'true'">deprecated</xsl:when>
@@ -143,15 +147,15 @@
</xsl:choose>
</xsl:variable>
- <li class="{$bulletClass}" onclick="toggleVisibility('{@name}')">
+ <li class="{$bulletClass}" onclick="toggleVisibility('{$toggleId}')">
<span class="{$colorClass} {$deprecatedClass}">
<b><xsl:value-of select="@name" /></b>
<xsl:value-of select="@coveragePercentage" />%
(<xsl:value-of select="@numCovered" />/<xsl:value-of select="@numTotal" />)
</span>
- </li>
+ </li>
</xsl:template>
-
+
<xsl:template name="methodListItem">
<xsl:variable name="deprecatedClass">
@@ -166,6 +170,10 @@
<xsl:when test="@covered = 'true'">[X]</xsl:when>
<xsl:otherwise>[ ]</xsl:otherwise>
</xsl:choose>
+ <xsl:if test="@visibility != ''"> <xsl:value-of select="@visibility" /></xsl:if>
+ <xsl:if test="@abstract = 'true'"> abstract</xsl:if>
+ <xsl:if test="@static = 'true'"> static</xsl:if>
+ <xsl:if test="@final = 'true'"> final</xsl:if>
<xsl:if test="@returnType != ''"> <xsl:value-of select="@returnType" /></xsl:if>
<b> <xsl:value-of select="@name" /></b><xsl:call-template name="formatParameters" />
</span>
diff --git a/tools/utils/buildCts.py b/tools/utils/buildCts.py
index e7a2f3c..0531c49 100755
--- a/tools/utils/buildCts.py
+++ b/tools/utils/buildCts.py
@@ -503,6 +503,11 @@
'android.content.cts.ContentResolverTest#testUnstableToStableRefs',
'android.content.cts.ContentResolverTest#testUpdate',
'android.content.cts.ContentResolverTest#testValidateSyncExtrasBundle',],
+ 'android.bluetooth' : [
+ 'android.bluetooth.cts.BluetoothLeScanTest#testBasicBleScan',
+ 'android.bluetooth.cts.BluetoothLeScanTest#testBatchScan',
+ 'android.bluetooth.cts.BluetoothLeScanTest#testOpportunisticScan',
+ 'android.bluetooth.cts.BluetoothLeScanTest#testScanFilter',],
'' : []}
def LogGenerateDescription(name):