Merge "Tests for app "category" API."
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 4668541..ce1acbc 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -74,7 +74,8 @@
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIbK6ldcOzoeRtQ1u1dFVJ1A7KetRhit-a1Xa82Q" />
-
+ <meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
+ android:value="true"/>
<uses-library android:name="android.test.runner"/>
<activity android:name=".TestListActivity" android:label="@string/app_name" />
@@ -2272,6 +2273,48 @@
</intent-filter>
</activity>
+ <activity android:name=".managedprovisioning.EnterprisePrivacyTestDefaultAppActivity"
+ android:label="@string/enterprise_privacy_default_app"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="http" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.media.action.IMAGE_CAPTURE" />
+ <action android:name="android.media.action.IMAGE_CAPTURE_SECURE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="geo" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.SENDTO" />
+ <action android:name="android.intent.action.SEND" />
+ <action android:name="android.intent.action.SEND_MULTIPLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.INSERT" />
+ <data android:mimeType="vnd.android.cursor.dir/event" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PICK" />
+ <data android:mimeType="vnd.android.cursor.dir/contact" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <action android:name="android.intent.action.CALL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".managedprovisioning.CommandReceiverActivity"
android:theme="@android:style/Theme.NoDisplay"
android:noHistory="true">
@@ -2856,6 +2899,39 @@
android:value="android.hardware.telephony"/>
</activity>
+ <activity
+ android:name=".voicemail.CallSettingsCheckActivity"
+ android:label="@string/call_settings_check_test">
+ <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_telephony"/>
+ <meta-data
+ android:name="test_required_features"
+ android:value="android.hardware.telephony"/>
+ </activity>
+
+ <activity
+ android:name=".voicemail.VoicemailSettingsCheckActivity"
+ android:label="@string/ringtone_settings_check_test">
+ <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_telephony"/>
+ <meta-data
+ android:name="test_required_features"
+ android:value="android.hardware.telephony"/>
+ </activity>
+
+
<service
android:name=".voicemail.CtsVisualVoicemailService"
android:permission="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
diff --git a/apps/CtsVerifier/res/layout/voicemail_hide_in_call_settings.xml b/apps/CtsVerifier/res/layout/voicemail_hide_in_call_settings.xml
new file mode 100644
index 0000000..e7a832c
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/voicemail_hide_in_call_settings.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/call_settings_check_instructions"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/set_default_dialer_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/voicemail_set_default_dialer_description"
+ android:textSize="16dp"/>
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/set_default_dialer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/voicemail_set_default_dialer_button"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/open_call_settings_explanation"
+ android:textSize="16dp"/>
+ <Button
+ android:id="@+id/open_call_settings"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/open_call_settings"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <Button
+ android:id="@+id/settings_hidden"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/voicemail_hidden"/>
+ <Button
+ android:id="@+id/settings_not_hidden"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/voicemail_not_hidden"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/restore_default_dialer_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:id="@+id/restore_default_dialer_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/voicemail_restore_default_dialer_description"
+ android:textSize="16dp"/>
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/restore_default_dialer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/voicemail_restore_default_dialer_button"/>
+
+ <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/voicemail_hide_ringtone_settings.xml b/apps/CtsVerifier/res/layout/voicemail_hide_ringtone_settings.xml
new file mode 100644
index 0000000..56b815c
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/voicemail_hide_ringtone_settings.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/ringtone_settings_check_instructions"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/open_voicemail_settings_explanation"
+ android:textSize="16dp"/>
+ <Button
+ android:id="@+id/open_voicemail_settings"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/open_voicemail_settings"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <Button
+ android:id="@+id/settings_hidden"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/ringtone_hidden"/>
+ <Button
+ android:id="@+id/settings_not_hidden"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/ringtone_not_hidden"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/restore_default_dialer_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:id="@+id/restore_default_dialer_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/voicemail_restore_default_dialer_description"
+ android:textSize="16dp"/>
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/restore_default_dialer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/voicemail_restore_default_dialer_button"/>
+
+ <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/raw/company_terms_content.txt b/apps/CtsVerifier/res/raw/company_terms_content.txt
new file mode 100644
index 0000000..ca0623c
--- /dev/null
+++ b/apps/CtsVerifier/res/raw/company_terms_content.txt
@@ -0,0 +1 @@
+Company Terms Content.
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index c85c3542..e00bbfb 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1468,6 +1468,7 @@
<string name="nls_hints">Check that the listener can set hints.</string>
<string name="nls_snooze_one">Check that service can snooze a notification.</string>
<string name="nls_snooze_one_time">Check that service can snooze a notification for a given time.</string>
+ <string name="nls_get_snoozed">Check that service can retrieve snoozed notifications.</string>
<string name="nls_unsnooze_one">Check that service can unsnooze a notification.</string>
<string name="nas_note_missed_enqueued">Check that notification was not enqueued.</string>
<string name="cp_test">Condition Provider test</string>
@@ -1872,7 +1873,7 @@
<string name="provisioning_tests_byod_custom_color"> Custom provisioning color </string>
<string name="provisioning_tests_byod_custom_color_info">
Please press the Go button to start the provisioning.
- Check that provisioning is colorized in green.
+ Check that the top status bar and "Accept and continue" button are colorized in green.
Then hit back and stop the provisioning.
</string>
<string name="provisioning_tests_byod_custom_image"> Custom provisioning image </string>
@@ -1881,6 +1882,14 @@
Check that the CtsVerifier logo is displayed at the top of the page.
Then hit back and stop the provisioning.
</string>
+ <string name="provisioning_tests_byod_custom_terms">Custom terms</string>
+ <string name="provisioning_tests_byod_custom_terms_instructions">
+ 1. Please press the Go button to start the provisioning.\n
+ 2. Click \"View Terms\" button\n
+ 3. Expand \"Company ABC\" section. Verify the section content is \"Company Terms Content.\"\n
+ 4. Then hit back twice and stop the provisioning.
+ </string>
+ <string name="provisioning_tests_byod_custom_term_header1">Company ABC</string>
<!-- Strings for BYOD managed provisioning (ByodFlowTestActivity) -->
<string name="test_category_managed_provisioning">Managed Provisioning</string>
@@ -2783,11 +2792,14 @@
<string name="enterprise_privacy_page_info">
Please press the Go button to open Settings. Verify that:\n
1) There is an entry for system privacy.\n
- 2) Tapping that entry opens a screen in which you are told your organization can:\n
- 3) Change settings on this device.\n
- 4) See data associated with your work account.\n
- 5) See the list of all apps on your device.\n
- 6) See usage of each app on your device.\n
+ 2) Tapping that entry opens a screen.\n
+ 3) In this screen, you are told your organization can:\n
+ * Change settings on this device.\n
+ * See data associated with your work account.\n
+ * See the list of all apps on your device.\n
+ * See usage of each app on your device.\n
+ * Lock the device and change the password.\n
+ * Wipe the device.\n
\n
Use the Back button to return to this page.
</string>
@@ -2881,6 +2893,18 @@
6) Repeat steps (2) through (4), verifying that in step (3), you are told now that your administrator has allowed one app to access your camera.\n
7) Press the Reset button.
</string>
+ <string name="enterprise_privacy_default_apps">Default apps</string>
+ <string name="enterprise_privacy_default_apps_info">
+ Please do the following:\n
+ 1) Press the Reset button.\n
+ 2) Press the Open Settings button.\n
+ 3) In the screen that opens, verify that you are not told that your administrator set any default apps.\n
+ 4) Use the Back button to return to this page.\n
+ 5) Press the Set Default Apps button.\n
+ 6) Repeat steps (2) through (4), verifying that in step (3), you are told now that your administrator has set seven default apps.\n
+ 7) Press the Reset button.
+ </string>
+ <string name="enterprise_privacy_set_default_apps">Set Default Apps</string>
<string name="enterprise_privacy_always_on_vpn">Always-on VPN</string>
<string name="enterprise_privacy_always_on_vpn_info">
Please do the following:\n
@@ -2964,6 +2988,7 @@
9) Verify that a screen informing you what your managing organization can do is shown.\n
11) Use the Back button to return to this page.
</string>
+ <string name="enterprise_privacy_default_app">CTS Verifier Test</string>
<!-- Strings for Network Logging Tests -->
<string name="device_owner_network_logging_ui">Network Logging UI</string>
@@ -3524,5 +3549,31 @@
<string name="visual_voicemail_service_remove_sim">Remove SIM</string>
<string name="visual_voicemail_service_remove_sim_received">SIM removal event received</string>
+ <!-- Strings for CallSettingsCheck test -->
+ <string name="call_settings_check_test">Hide voicemail in call settings test</string>
+ <string name="call_settings_check_instructions">This test verifies that the default dialer can
+ hide voicemail settings in the call settings menu by using
+ TelephonyManager.METADATA_HIDE_VOICEMAIL_SETTINGS_MENU
+ </string>
+ <string name="open_call_settings_explanation">Tap the button, and verify that \"voicemail\" does
+ not exist in the call settings
+ </string>
+ <string name="open_call_settings">Open call settings</string>
+ <string name="voicemail_hidden">\"Voicemail\" does not exist</string>
+ <string name="voicemail_not_hidden">\"Voicemail\" exists</string>
+
+
+ <!-- Strings for VoicemailSettingsCheck test -->
+ <string name="ringtone_settings_check_test">Hide settings in voicemail test</string>
+ <string name="ringtone_settings_check_instructions">This test verifies that voicemail settings
+ accessible with public API can be hidden when launching
+ TelephonyManager.ACTION_CONFIGURE_VOICEMAIL with EXTRA_HIDE_PUBLIC_SETTINGS.
+ </string>
+ <string name="open_voicemail_settings_explanation">Tap the button, ringtone and virbration
+ settings does not exist in the voicemail settings.
+ </string>
+ <string name="open_voicemail_settings">Open voicemail settings</string>
+ <string name="ringtone_hidden">Ringtone settings does not exist</string>
+ <string name="ringtone_not_hidden">Ringtone settings exists</string>
</resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodProvisioningTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodProvisioningTestActivity.java
index dfbbb31..6856d2f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodProvisioningTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodProvisioningTestActivity.java
@@ -44,14 +44,17 @@
R.string.provisioning_tests_byod_custom_color,
R.string.provisioning_tests_byod_custom_color_info,
new ButtonInfo(R.string.go_button_text, colorIntent)));
- Uri logoUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getPackageName()
- + "/" + R.drawable.icon);
+ Uri logoUri = getResourceUri(R.drawable.icon);
Intent imageIntent = new Intent(this, ProvisioningStartingActivity.class)
.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_LOGO_URI, logoUri);
adapter.add(Utils.createInteractiveTestItem(this, "BYOD_CustomImage",
R.string.provisioning_tests_byod_custom_image,
R.string.provisioning_tests_byod_custom_image_info,
new ButtonInfo(R.string.go_button_text, imageIntent)));
+ adapter.add(Utils.createInteractiveTestItem(this, "BYOD_CustomTerms",
+ R.string.provisioning_tests_byod_custom_terms,
+ R.string.provisioning_tests_byod_custom_terms_instructions,
+ new ButtonInfo(R.string.go_button_text, getTestTermsIntent())));
setTestListAdapter(adapter);
}
@@ -81,4 +84,24 @@
finish();
}
}
+
+ private Intent getTestTermsIntent() {
+ Bundle bundle = new Bundle();
+ bundle.putString(DevicePolicyManager.EXTRA_PROVISIONING_DISCLAIMER_HEADER,
+ getString(R.string.provisioning_tests_byod_custom_term_header1));
+ bundle.putParcelable(DevicePolicyManager.EXTRA_PROVISIONING_DISCLAIMER_CONTENT,
+ getResourceUri(R.raw.company_terms_content));
+
+ return new Intent(this, ProvisioningStartingActivity.class)
+ .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DISCLAIMERS,
+ new Bundle[] { bundle });
+ }
+
+ private Uri getResourceUri(int resId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(getPackageName())
+ .appendPath(getResources().getResourceTypeName(resId))
+ .appendPath(getResources().getResourceEntryName(resId))
+ .build();
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 028639d..28c6cfa 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -23,12 +23,16 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
import android.graphics.BitmapFactory;
import android.net.ProxyInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
import android.util.Log;
import com.android.cts.verifier.R;
@@ -78,6 +82,10 @@
public static final String COMMAND_INSTALL_HELPER_PACKAGE = "install-helper-package";
public static final String COMMAND_UNINSTALL_HELPER_PACKAGE = "uninstall-helper-package";
public static final String COMMAND_SET_PERMISSION_GRANT_STATE = "set-permission-grant-state";
+ public static final String COMMAND_ADD_PERSISTENT_PREFERRED_ACTIVITIES =
+ "add-persistent-preferred-activities";
+ public static final String COMMAND_CLEAR_PERSISTENT_PREFERRED_ACTIVITIES =
+ "clear-persistent-preferred-activities";
public static final String COMMAND_CREATE_MANAGED_PROFILE = "create-managed-profile";
public static final String COMMAND_REMOVE_MANAGED_PROFILE = "remove-managed-profile";
public static final String COMMAND_SET_ALWAYS_ON_VPN = "set-always-on-vpn";
@@ -267,6 +275,57 @@
intent.getIntExtra(EXTRA_GRANT_STATE,
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT));
} break;
+ case COMMAND_ADD_PERSISTENT_PREFERRED_ACTIVITIES: {
+ final ComponentName componentName =
+ EnterprisePrivacyTestDefaultAppActivity.COMPONENT_NAME;
+ // Browser
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_VIEW);
+ filter.addCategory(Intent.CATEGORY_BROWSABLE);
+ filter.addDataScheme("http");
+ mDpm.addPersistentPreferredActivity(mAdmin, filter, componentName);
+ // Camera
+ filter = new IntentFilter();
+ filter.addAction(MediaStore.ACTION_IMAGE_CAPTURE);
+ filter.addAction(MediaStore.ACTION_VIDEO_CAPTURE);
+ mDpm.addPersistentPreferredActivity(mAdmin, filter, componentName);
+ // Map
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_VIEW);
+ filter.addDataScheme("geo");
+ mDpm.addPersistentPreferredActivity(mAdmin, filter, componentName);
+ // E-mail
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SENDTO);
+ filter.addAction(Intent.ACTION_SEND);
+ filter.addAction(Intent.ACTION_SEND_MULTIPLE);
+ mDpm.addPersistentPreferredActivity(mAdmin, filter, componentName);
+ // Calendar
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_INSERT);
+ filter.addDataType("vnd.android.cursor.dir/event");
+ mDpm.addPersistentPreferredActivity(mAdmin, filter, componentName);
+ // Contacts
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PICK);
+ filter.addDataType(ContactsContract.Contacts.CONTENT_TYPE);
+ mDpm.addPersistentPreferredActivity(mAdmin, filter, componentName);
+ // Dialer
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_DIAL);
+ filter.addAction(Intent.ACTION_CALL);
+ mDpm.addPersistentPreferredActivity(mAdmin, filter, componentName);
+ getPackageManager().setComponentEnabledSetting(componentName,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ } break;
+ case COMMAND_CLEAR_PERSISTENT_PREFERRED_ACTIVITIES: {
+ mDpm.clearPackagePersistentPreferredActivities(mAdmin, getPackageName());
+ getPackageManager().setComponentEnabledSetting(
+ EnterprisePrivacyTestDefaultAppActivity.COMPONENT_NAME,
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+ } break;
case COMMAND_CREATE_MANAGED_PROFILE: {
if (!mDpm.isDeviceOwnerApp(getPackageName())) {
return;
@@ -383,11 +442,16 @@
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
mDpm.setPermissionGrantState(mAdmin, getPackageName(), Manifest.permission.CAMERA,
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+ mDpm.clearPackagePersistentPreferredActivities(mAdmin, getPackageName());
mDpm.setAlwaysOnVpnPackage(mAdmin, null, false);
mDpm.setRecommendedGlobalProxy(mAdmin, null);
uninstallHelperPackage();
removeManagedProfile();
+ getPackageManager().setComponentEnabledSetting(
+ EnterprisePrivacyTestDefaultAppActivity.COMPONENT_NAME,
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
}
private void clearProfileOwnerRelatedPolicies() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestDefaultAppActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestDefaultAppActivity.java
new file mode 100644
index 0000000..ed7d8f4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestDefaultAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.managedprovisioning;
+
+import android.app.Activity;
+import android.content.ComponentName;
+
+public class EnterprisePrivacyTestDefaultAppActivity extends Activity {
+
+ public static final ComponentName COMPONENT_NAME = new ComponentName("com.android.cts.verifier",
+ EnterprisePrivacyTestDefaultAppActivity.class.getName());
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
index 54f1b79..c06ba2b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
@@ -52,6 +52,8 @@
= "ENTERPRISE_PRIVACY_MICROPHONE_ACCESS";
private static final String ENTERPRISE_PRIVACY_CAMERA_ACCESS
= "ENTERPRISE_PRIVACY_CAMERA_ACCESS";
+ private static final String ENTERPRISE_PRIVACY_DEFAULT_APPS
+ = "ENTERPRISE_PRIVACY_DEFAULT_APPS";
private static final String ENTERPRISE_PRIVACY_ALWAYS_ON_VPN
= "ENTERPRISE_PRIVACY_ALWAYS_ON_VPN";
private static final String ENTERPRISE_PRIVACY_COMP_ALWAYS_ON_VPN
@@ -167,6 +169,18 @@
R.string.enterprise_privacy_admin_granted_camera_access,
R.string.enterprise_privacy_admin_granted_camera_access_info,
Manifest.permission.CAMERA));
+ adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_DEFAULT_APPS,
+ R.string.enterprise_privacy_default_apps,
+ R.string.enterprise_privacy_default_apps_info,
+ new ButtonInfo[] {
+ new ButtonInfo(R.string.enterprise_privacy_open_settings,
+ new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
+ new ButtonInfo(R.string.enterprise_privacy_reset, buildCommandIntent(
+ CommandReceiverActivity
+ .COMMAND_CLEAR_PERSISTENT_PREFERRED_ACTIVITIES)),
+ new ButtonInfo(R.string.enterprise_privacy_set_default_apps,
+ buildCommandIntent(CommandReceiverActivity
+ .COMMAND_ADD_PERSISTENT_PREFERRED_ACTIVITIES))}));
adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_ALWAYS_ON_VPN,
R.string.enterprise_privacy_always_on_vpn,
R.string.enterprise_privacy_always_on_vpn_info,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
index 1f86f64..36a502c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
@@ -62,6 +62,7 @@
public static final String SERVICE_ADJUST_ENQUEUE = SERVICE_BASE + "ADJUST_ENQUEUE";
public static final String SERVICE_SNOOZE_UNTIL_CONTEXT = SERVICE_BASE + "CONTEXT_SNOOZE";
public static final String SERVICE_SNOOZED = SERVICE_BASE + "SNOOZED_UNTIL_CONTEXT";
+ public static final String SERVICE_UNSNOOZE_ONE = SERVICE_BASE + "SERVICE_UNSNOOZE_ONE";
static final String EXTRA_PAYLOAD = "PAYLOAD";
static final String EXTRA_INT = "INT";
@@ -226,6 +227,10 @@
String snoozeCriterionId = intent.getStringExtra(EXTRA_PAYLOAD);
MockAssistant.this.snoozeNotification(
mNotificationKeys.get(tag), snoozeCriterionId);
+ } else if (SERVICE_UNSNOOZE_ONE.equals(action)) {
+ String tag = intent.getStringExtra(EXTRA_TAG);
+ String key = mNotificationKeys.get(tag);
+ MockAssistant.this.unsnoozeNotification(key);
} else {
Log.w(TAG, "unknown action");
setResultCode(Activity.RESULT_CANCELED);
@@ -252,6 +257,7 @@
filter.addAction(SERVICE_ADJUST_ENQUEUE);
filter.addAction(SERVICE_SNOOZE_UNTIL_CONTEXT);
filter.addAction(SERVICE_SNOOZED);
+ filter.addAction(SERVICE_UNSNOOZE_ONE);
registerReceiver(mReceiver, filter);
}
@@ -403,10 +409,6 @@
requestStringListResult(context, SERVICE_POSTED, catcher);
}
- public static void probeListenerOrder(Context context, StringListResultCatcher catcher) {
- requestStringListResult(context, SERVICE_ORDER, catcher);
- }
-
public static void probeListenerPayloads(Context context, BundleListResultCatcher catcher) {
requestBundleListResult(context, SERVICE_PAYLOADS, catcher);
}
@@ -487,6 +489,10 @@
context.sendBroadcast(broadcast);
}
+ public static void unsnoozeOne(Context context, String tag) {
+ sendCommand(context, SERVICE_UNSNOOZE_ONE, tag, 0);
+ }
+
public abstract static class StatusCatcher extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
index 6c294e5..4440104 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
@@ -56,9 +56,8 @@
static final String SERVICE_PROBE_HINTS = SERVICE_BASE + "SERVICE_PROBE_HINTS";
static final String SERVICE_ORDER = SERVICE_BASE + "SERVICE_ORDER";
static final String SERVICE_DND = SERVICE_BASE + "SERVICE_DND";
- static final String SERVICE_SNOOZE_ONE = SERVICE_BASE + "SERVICE_SNOOZE_ONE";
- static final String SERVICE_UNSNOOZE_ONE = SERVICE_BASE + "SERVICE_UNSNOOZE_ONE";
static final String SERVICE_SNOOZE_UNTIL = SERVICE_BASE + "SERVICE_SNOOZE_UNTIL";
+ static final String SERVICE_GET_SNOOZED = SERVICE_BASE + "GET_SNOOZED";
static final String EXTRA_PAYLOAD = "PAYLOAD";
static final String EXTRA_INT = "INT";
@@ -85,6 +84,7 @@
private ArrayMap<String, String> mNotificationKeys = new ArrayMap<>();
private ArrayList<String> mRemoved = new ArrayList<String>();
private ArrayMap<String, JSONObject> mRemovedReason = new ArrayMap<>();
+ private ArrayList<String> mSnoozed = new ArrayList<>();
private ArrayList<String> mOrder = new ArrayList<>();
private Set<String> mTestPackages = new HashSet<>();
private BroadcastReceiver mReceiver;
@@ -100,6 +100,7 @@
mPosted = new ArrayList<String>();
mRemoved = new ArrayList<String>();
+ mSnoozed = new ArrayList<String>();
mReceiver = new BroadcastReceiver() {
@Override
@@ -161,19 +162,20 @@
bundle.putInt(EXTRA_INT, MockListener.this.getCurrentListenerHints());
setResultExtras(bundle);
setResultCode(Activity.RESULT_OK);
- } else if (SERVICE_SNOOZE_ONE.equals(action)) {
- String tag = intent.getStringExtra(EXTRA_TAG);
- String key = mNotificationKeys.get(tag);
- MockListener.this.snoozeNotification(key);
- } else if (SERVICE_UNSNOOZE_ONE.equals(action)) {
- String tag = intent.getStringExtra(EXTRA_TAG);
- String key = mNotificationKeys.get(tag);
- MockListener.this.unsnoozeNotification(key);
} else if (SERVICE_SNOOZE_UNTIL.equals(action)) {
String tag = intent.getStringExtra(EXTRA_TAG);
String key = mNotificationKeys.get(tag);
MockListener.this.snoozeNotification(key,
intent.getLongExtra(EXTRA_LONG, (long) 0));
+ } else if (SERVICE_GET_SNOOZED.equals(action)) {
+ mSnoozed.clear();
+ StatusBarNotification[] snoozed = MockListener.this.getSnoozedNotifications();
+ for (StatusBarNotification sbn : snoozed) {
+ mSnoozed.add(sbn.getTag());
+ }
+ bundle.putStringArrayList(EXTRA_PAYLOAD, mSnoozed);
+ setResultExtras(bundle);
+ setResultCode(Activity.RESULT_OK);
} else {
Log.w(TAG, "unknown action");
setResultCode(Activity.RESULT_CANCELED);
@@ -194,9 +196,8 @@
filter.addAction(SERVICE_SNOOZE);
filter.addAction(SERVICE_HINTS);
filter.addAction(SERVICE_PROBE_HINTS);
- filter.addAction(SERVICE_SNOOZE_ONE);
- filter.addAction(SERVICE_UNSNOOZE_ONE);
filter.addAction(SERVICE_SNOOZE_UNTIL);
+ filter.addAction(SERVICE_GET_SNOOZED);
registerReceiver(mReceiver, filter);
}
@@ -228,6 +229,7 @@
mRemoved.clear();
mOrder.clear();
mRemovedReason.clear();
+ mSnoozed.clear();
}
@Override
@@ -316,6 +318,10 @@
requestStringListResult(context, SERVICE_POSTED, catcher);
}
+ public static void probeListenerSnoozed(Context context, StringListResultCatcher catcher) {
+ requestStringListResult(context, SERVICE_GET_SNOOZED, catcher);
+ }
+
public static void probeListenerOrder(Context context, StringListResultCatcher catcher) {
requestStringListResult(context, SERVICE_ORDER, catcher);
}
@@ -351,14 +357,6 @@
sendCommand(context, SERVICE_CLEAR_ONE, tag, code);
}
- public static void snoozeOne(Context context, String tag) {
- sendCommand(context, SERVICE_SNOOZE_ONE, tag, 0);
- }
-
- public static void unsnoozeOne(Context context, String tag) {
- sendCommand(context, SERVICE_UNSNOOZE_ONE, tag, 0);
- }
-
public static void snoozeOneUntil(Context context, String tag, long until) {
Intent broadcast = new Intent(SERVICE_SNOOZE_UNTIL);
if (tag != null) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
index 8d5b911..540240d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
@@ -117,6 +117,7 @@
tests.add(new AdjustNotificationTest());
tests.add(new AdjustEnqueuedNotificationTest());
tests.add(new SnoozeNotificationUntilContextTest());
+ tests.add(new UnsnoozeNotificationTest());
tests.add(new IsDisabledTest());
tests.add(new ServiceStoppedTest());
tests.add(new NotificationNotEnqueuedTest());
@@ -1025,8 +1026,8 @@
}
private class CreateChannelTest extends InteractiveTestCase {
- private NotificationChannel channel = new NotificationChannel("channelForOtherPkg", "new",
- NotificationManager.IMPORTANCE_LOW);
+ private NotificationChannel channel = new NotificationChannel(UUID.randomUUID().toString(),
+ "new", NotificationManager.IMPORTANCE_LOW);
@Override
View inflate(ViewGroup parent) {
@@ -1074,8 +1075,8 @@
}
private class DeleteChannelTest extends InteractiveTestCase {
- private NotificationChannel channel = new NotificationChannel("1channelForOtherPkg", "new",
- NotificationManager.IMPORTANCE_LOW);
+ private NotificationChannel channel = new NotificationChannel(UUID.randomUUID().toString(),
+ "new", NotificationManager.IMPORTANCE_LOW);
@Override
View inflate(ViewGroup parent) {
@@ -1098,6 +1099,7 @@
public void accept(ArrayList<Parcelable> result) {
if (result == null || result.size() == 0) {
status = FAIL;
+ logFail();
return;
}
boolean pass = false;
@@ -1110,6 +1112,7 @@
mContext, OTHER_PKG, channel.getId());
status = RETEST;
}
+ delay();
}
});
} else if (status == RETEST) {
@@ -1120,6 +1123,7 @@
if (result == null || result.size() <= 1) {
status = PASS;
} else {
+ logFail();
status = FAIL;
}
next();
@@ -1139,7 +1143,7 @@
}
private class UpdateChannelTest extends InteractiveTestCase {
- private String id = "channelToUpdate";
+ private String id = UUID.randomUUID().toString();
private NotificationChannel channel = new NotificationChannel(id, "new",
NotificationManager.IMPORTANCE_LOW);
private NotificationChannel updatedChannel = new NotificationChannel(id, "new",
@@ -1241,7 +1245,7 @@
}
sendNotifications(channel);
status = READY;
- delay();
+ delay(6000);
}
@Override
@@ -1283,6 +1287,10 @@
@Override
void tearDown() {
MockAssistant.deleteChannel(mContext, THIS_PKG, channel.getId());
+ mNm.cancel(mTag1, mId1);
+ mNm.cancel(mTag2, mId2);
+ mNm.cancel(mTag2, mId3);
+ MockAssistant.resetListenerData(mContext);
delay();
}
}
@@ -1378,6 +1386,83 @@
}
}
+ private class UnsnoozeNotificationTest extends InteractiveTestCase {
+ final static int READY_TO_SNOOZE = 0;
+ final static int SNOOZED = 1;
+ final static int READY_TO_UNSNOOZE = 2;
+ final static int UNSNOOZED = 3;
+ int state = -1;
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_unsnooze_one);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications();
+ status = READY;
+ delay();
+ }
+
+ @Override
+ void test() {
+ status = RETEST;
+
+ if (state == READY_TO_SNOOZE) {
+ MockAssistant.snoozeUntilContext(mContext, mTag1, "hello");
+ state = SNOOZED;
+ } else if (state == SNOOZED) {
+ MockAssistant.probeListenerRemovedWithReason(mContext,
+ new MockAssistant.BundleListResultCatcher() {
+ @Override
+ public void accept(ArrayList<Parcelable> result) {
+ if (result == null || result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (Parcelable payloadData : result) {
+ Bundle payload = (Bundle) payloadData;
+ pass &= checkEquals(mTag1,
+ payload.getString(KEY_TAG),
+ "data dismissal test: notification tag (%s, %s)");
+ pass &= checkEquals(MockAssistant.REASON_SNOOZED,
+ payload.getInt(KEY_REASON),
+ "data dismissal test: reason (%d, %d)");
+ }
+ status = pass ? PASS : FAIL;
+ next();
+ }
+ });
+ } else if (state == READY_TO_UNSNOOZE) {
+ MockAssistant.unsnoozeOne(mContext, mTag1);
+ state = UNSNOOZED;
+ } else {
+ MockAssistant.probeListenerPosted(mContext,
+ new MockAssistant.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> result) {
+ if (result != null && result.size() != 0
+ && result.contains(mTag1)) {
+ status = PASS;
+ } else {
+ logFail();
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ }
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockAssistant.resetListenerData(mContext);
+ delay();
+ }
+ }
+
private boolean compareChannels(NotificationChannel expected, NotificationChannel actual) {
boolean pass = true;
String msg = "Channel mismatch (%s, %s)";
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index fe23fe0..38fc5bd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -79,9 +79,8 @@
tests.add(new DismissOneTest());
tests.add(new DismissOneWithReasonTest());
tests.add(new DismissAllTest());
- tests.add(new SnoozeNotificationTest());
- tests.add(new UnsnoozeNotificationTest());
tests.add(new SnoozeNotificationForTimeTest());
+ tests.add(new GetSnoozedNotificationTest());
tests.add(new EnableHintsTest());
tests.add(new SnoozeTest());
tests.add(new UnsnoozeTest());
@@ -666,143 +665,6 @@
}
}
- private class SnoozeNotificationTest extends InteractiveTestCase {
- @Override
- View inflate(ViewGroup parent) {
- return createAutoItem(parent, R.string.nls_snooze_one);
- }
-
- @Override
- void setUp() {
- sendNotifications();
- status = READY;
- delay();
- }
-
- @Override
- void test() {
- if (status == READY) {
- MockListener.snoozeOne(mContext, mTag1);
- status = RETEST;
- } else {
- MockListener.probeListenerRemoved(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- if (result != null && result.size() != 0
- && result.contains(mTag1)
- && !result.contains(mTag2)
- && !result.contains(mTag3)) {
- status = PASS;
- } else {
- logFail();
- status = FAIL;
- }
- next();
- }
- });
- }
- delay();
- }
-
- @Override
- void tearDown() {
- mNm.cancel(mTag1, mId1);
- mNm.cancel(mTag2, mId2);
- mNm.cancel(mTag2, mId3);
- MockListener.resetListenerData(mContext);
- delay();
- }
- }
-
- private class UnsnoozeNotificationTest extends InteractiveTestCase {
- final static int READY_TO_SNOOZE = 0;
- final static int SNOOZED = 1;
- final static int READY_TO_UNSNOOZE = 2;
- final static int UNSNOOZED = 3;
- int state = -1;
- @Override
- View inflate(ViewGroup parent) {
- return createAutoItem(parent, R.string.nls_unsnooze_one);
- }
-
- @Override
- void setUp() {
- sendNotifications();
- status = READY;
- delay();
- }
-
- @Override
- void test() {
- status = RETEST;
-
- if (state == READY_TO_SNOOZE) {
- MockListener.snoozeOne(mContext, mTag1);
- state = SNOOZED;
- } else if (state == SNOOZED) {
- MockListener.probeListenerRemovedWithReason(mContext,
- new StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- if (result == null || result.size() == 0) {
- status = FAIL;
- return;
- }
- boolean pass = true;
- for (String payloadData : result) {
- JSONObject payload = null;
- try {
- payload = new JSONObject(payloadData);
- pass &= checkEquals(mTag1,
- payload.getString(JSON_TAG),
- "data dismissal test: notification tag (%s, %s)");
- pass &= checkEquals(MockListener.REASON_SNOOZED,
- payload.getInt(JSON_TAG),
- "data dismissal test: reason (%d, %d)");
- } catch (JSONException e) {
- e.printStackTrace();
- }
- }
- if (!pass) {
- logFail();
- status = FAIL;
- next();
- return;
- } else {
- state = READY_TO_UNSNOOZE;
- }
- }
- });
- } else if (state == READY_TO_UNSNOOZE) {
- MockListener.unsnoozeOne(mContext, mTag1);
- state = UNSNOOZED;
- } else {
- MockListener.probeListenerPosted(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- if (result != null && result.size() != 0
- && result.contains(mTag1)) {
- status = PASS;
- } else {
- logFail();
- status = FAIL;
- }
- next();
- }
- });
- }
- }
-
- @Override
- void tearDown() {
- mNm.cancelAll();
- MockListener.resetListenerData(mContext);
- delay();
- }
- }
-
private class SnoozeNotificationForTimeTest extends InteractiveTestCase {
final static int READY_TO_SNOOZE = 0;
final static int SNOOZED = 1;
@@ -889,4 +751,94 @@
delay();
}
}
+
+ private class GetSnoozedNotificationTest extends InteractiveTestCase {
+ final static int READY_TO_SNOOZE = 0;
+ final static int SNOOZED = 1;
+ final static int READY_TO_CHECK_FOR_GET_SNOOZE = 2;
+ int state = -1;
+ long snoozeTime = 30000;
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_get_snoozed);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications();
+ status = READY;
+ state = READY_TO_SNOOZE;
+ delay();
+ }
+
+ @Override
+ void test() {
+ status = RETEST;
+ if (state == READY_TO_SNOOZE) {
+ MockListener.snoozeOneUntil(mContext, mTag1,
+ System.currentTimeMillis() + snoozeTime);
+ MockListener.snoozeOneUntil(mContext, mTag2,
+ System.currentTimeMillis() + snoozeTime);
+ state = SNOOZED;
+ } else if (state == SNOOZED){
+ MockListener.probeListenerRemovedWithReason(mContext,
+ new StringListResultCatcher() {
+ @Override
+ public void accept(List<String> result) {
+ if (result == null || result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (String payloadData : result) {
+ JSONObject payload = null;
+ try {
+ payload = new JSONObject(payloadData);
+ pass &= checkEquals(mTag1,
+ payload.getString(JSON_TAG),
+ "data dismissal test: notification tag (%s, %s)");
+ pass &= checkEquals(MockListener.REASON_SNOOZED,
+ payload.getInt(JSON_TAG),
+ "data dismissal test: reason (%d, %d)");
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ if (!pass) {
+ logFail();
+ status = FAIL;
+ next();
+ return;
+ } else {
+ state = READY_TO_CHECK_FOR_GET_SNOOZE;
+ }
+ }
+ });
+ } else {
+ MockListener.probeListenerSnoozed(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> result) {
+ if (result != null && result.size() == 2
+ && result.contains(mTag1)
+ && result.contains(mTag2)) {
+ status = PASS;
+ } else {
+ logFail();
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ }
+ delay();
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/CallSettingsCheckActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/CallSettingsCheckActivity.java
new file mode 100644
index 0000000..ac0f060
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/CallSettingsCheckActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.voicemail;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.TelecomManager;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Tests {@link android.telephony.TelephonyManager#METADATA_HIDE_VOICEMAIL_SETTINGS_MENU}
+ */
+public class CallSettingsCheckActivity extends PassFailButtons.Activity {
+
+ private DefaultDialerChanger mDefaultDialerChanger;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ View view = getLayoutInflater().inflate(R.layout.voicemail_hide_in_call_settings, null);
+ setContentView(view);
+ setInfoResources(R.string.call_settings_check_test,
+ R.string.call_settings_check_instructions, -1);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+
+ mDefaultDialerChanger = new DefaultDialerChanger(this);
+
+ findViewById(R.id.open_call_settings).setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startActivity(new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS));
+ }
+ }
+ );
+
+ findViewById(R.id.settings_hidden).setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getPassButton().setEnabled(true);
+ setTestResultAndFinish(true);
+ }
+ }
+ );
+
+ findViewById(R.id.settings_not_hidden).setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setTestResultAndFinish(false);
+ }
+ }
+ );
+
+ }
+
+ @Override
+ protected void onDestroy() {
+ mDefaultDialerChanger.destroy();
+ super.onDestroy();
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/VoicemailSettingsCheckActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/VoicemailSettingsCheckActivity.java
new file mode 100644
index 0000000..d4ac0db
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/voicemail/VoicemailSettingsCheckActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.voicemail;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.TelephonyManager;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Tests {@link TelephonyManager.EXTRA_HIDE_PUBLIC_SETTINGS}
+ */
+public class VoicemailSettingsCheckActivity extends PassFailButtons.Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ View view = getLayoutInflater().inflate(R.layout.voicemail_hide_ringtone_settings, null);
+ setContentView(view);
+ setInfoResources(R.string.ringtone_settings_check_test,
+ R.string.ringtone_settings_check_instructions, -1);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+
+ findViewById(R.id.open_voicemail_settings).setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startActivity(new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL)
+ .putExtra(TelephonyManager.EXTRA_HIDE_PUBLIC_SETTINGS, true));
+ }
+ }
+ );
+
+ findViewById(R.id.settings_hidden).setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getPassButton().setEnabled(true);
+ setTestResultAndFinish(true);
+ }
+ }
+ );
+
+ findViewById(R.id.settings_not_hidden).setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setTestResultAndFinish(false);
+ }
+ }
+ );
+
+ }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java
new file mode 100644
index 0000000..4bd48bd
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+
+import android.os.SystemClock;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
+
+import org.hamcrest.Description;
+import org.mockito.ArgumentMatcher;
+import org.mockito.InOrder;
+
+public final class CtsMouseUtil {
+
+ private CtsMouseUtil() {}
+
+ public static View.OnHoverListener installHoverListener(View view) {
+ return installHoverListener(view, true);
+ }
+
+ public static View.OnHoverListener installHoverListener(View view, boolean result) {
+ final View.OnHoverListener mockListener = mock(View.OnHoverListener.class);
+ view.setOnHoverListener((v, event) -> {
+ // Clone the event to work around event instance reuse in the framework.
+ mockListener.onHover(v, MotionEvent.obtain(event));
+ return result;
+ });
+ return mockListener;
+ }
+
+ public static void clearHoverListener(View view) {
+ view.setOnHoverListener(null);
+ }
+
+ public static MotionEvent obtainMouseEvent(int action, View anchor, int offsetX, int offsetY) {
+ final long eventTime = SystemClock.uptimeMillis();
+ final int[] screenPos = new int[2];
+ anchor.getLocationOnScreen(screenPos);
+ final int x = screenPos[0] + offsetX;
+ final int y = screenPos[1] + offsetY;
+ MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ return event;
+ }
+
+ public static class ActionMatcher extends ArgumentMatcher<MotionEvent> {
+ private final int mAction;
+
+ public ActionMatcher(int action) {
+ mAction = action;
+ }
+
+ public boolean matches(Object actual) {
+ return (actual instanceof MotionEvent) && ((MotionEvent) actual).getAction() == mAction;
+ }
+
+ public void describeTo(Description description) {
+ description.appendText("action=" + MotionEvent.actionToString(mAction));
+ }
+ }
+
+ public static class PositionMatcher extends ActionMatcher {
+ private final int mX;
+ private final int mY;
+
+ public PositionMatcher(int action, int x, int y) {
+ super(action);
+ mX = x;
+ mY = y;
+ }
+
+ public boolean matches(Object actual) {
+ return super.matches(actual)
+ && ((int) ((MotionEvent) actual).getX()) == mX
+ && ((int) ((MotionEvent) actual).getY()) == mY;
+ }
+
+ public void describeTo(Description description) {
+ super.describeTo(description);
+ description.appendText("@(" + mX + "," + mY + ")");
+ }
+ }
+
+ public static void verifyEnterMove(View.OnHoverListener listener, View view, int moveCount) {
+ final InOrder inOrder = inOrder(listener);
+ verifyEnterMoveInternal(listener, view, moveCount, inOrder);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ public static void verifyEnterMoveExit(
+ View.OnHoverListener listener, View view, int moveCount) {
+ final InOrder inOrder = inOrder(listener);
+ verifyEnterMoveInternal(listener, view, moveCount, inOrder);
+ inOrder.verify(listener, times(1)).onHover(eq(view),
+ argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_EXIT)));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ private static void verifyEnterMoveInternal(
+ View.OnHoverListener listener, View view, int moveCount, InOrder inOrder) {
+ inOrder.verify(listener, times(1)).onHover(eq(view),
+ argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_ENTER)));
+ inOrder.verify(listener, times(moveCount)).onHover(eq(view),
+ argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_MOVE)));
+ }
+}
+
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
index 5b83357..c63b54a 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
@@ -51,9 +51,18 @@
};
private final Context mContext;
+ private final BlockingReceiver mManagedProfileProvisionedReceiver;
+ private final BlockingReceiver mManagedProfileAddedReceiver;
public SilentProvisioningTestManager(Context context) {
mContext = context.getApplicationContext();
+ mManagedProfileProvisionedReceiver =
+ new BlockingReceiver(mContext, ACTION_MANAGED_PROFILE_PROVISIONED);
+ mManagedProfileAddedReceiver = new BlockingReceiver(mContext, ACTION_MANAGED_PROFILE_ADDED);
+ }
+
+ public Intent getReceviedProfileProvisionedIntent() {
+ return mManagedProfileProvisionedReceiver.getReceivedIntent();
}
public boolean startProvisioningAndWait(Intent provisioningIntent) throws InterruptedException {
@@ -73,23 +82,19 @@
}
private boolean waitManagedProfileProvisioning() throws InterruptedException {
- BlockingReceiver managedProfileProvisionedReceiver = new BlockingReceiver(mContext,
- ACTION_MANAGED_PROFILE_PROVISIONED);
- managedProfileProvisionedReceiver.register();
- BlockingReceiver managedProfileAddedReceiver = new BlockingReceiver(mContext,
- ACTION_MANAGED_PROFILE_ADDED);
- managedProfileAddedReceiver.register();
+ mManagedProfileProvisionedReceiver.register();
+ mManagedProfileAddedReceiver.register();
if (!pollProvisioningResult()) {
return false;
}
- if (!managedProfileProvisionedReceiver.await()) {
+ if (!mManagedProfileProvisionedReceiver.await()) {
Log.i(TAG, "mManagedProfileProvisionedReceiver.await(): false");
return false;
}
- if (!managedProfileAddedReceiver.await()) {
+ if (!mManagedProfileAddedReceiver.await()) {
Log.i(TAG, "mManagedProfileAddedReceiver.await(): false");
return false;
}
@@ -132,14 +137,15 @@
private static class BlockingReceiver extends BroadcastReceiver {
- private static final CountDownLatch mLatch = new CountDownLatch(1);
-
+ private final CountDownLatch mLatch = new CountDownLatch(1);
private final Context mContext;
private final String mAction;
+ private Intent mReceivedIntent;
private BlockingReceiver(Context context, String action) {
mContext = context;
mAction = action;
+ mReceivedIntent = null;
}
public void register() {
@@ -150,8 +156,13 @@
return mLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
+ public Intent getReceivedIntent() {
+ return mReceivedIntent;
+ }
+
@Override
public void onReceive(Context context, Intent intent) {
+ mReceivedIntent = intent;
mLatch.countDown();
}
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
index fea9ce8..255c9f5 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.compatibility.common.tradefed.build;
+import com.android.compatibility.common.util.DynamicConfigHostSide;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IFolderBuildInfo;
@@ -40,7 +41,6 @@
private static final String SUITE_VERSION = "SUITE_VERSION";
private static final String SUITE_PLAN = "SUITE_PLAN";
private static final String START_TIME_MS = "START_TIME_MS";
- private static final String CONFIG_PATH_PREFIX = "DYNAMIC_CONFIG_FILE:";
private static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL";
private static final String COMMAND_LINE_ARGS = "command_line_args";
private static final String RETRY_COMMAND_LINE_ARGS = "retry_command_line_args";
@@ -104,7 +104,8 @@
}
public void addDynamicConfigFile(String moduleName, File configFile) {
- mBuildInfo.addBuildAttribute(CONFIG_PATH_PREFIX + moduleName, configFile.getAbsolutePath());
+ mBuildInfo.addBuildAttribute(DynamicConfigHostSide.CONFIG_PATH_PREFIX + moduleName,
+ configFile.getAbsolutePath());
}
public void setModuleIds(String[] moduleIds) {
@@ -114,8 +115,8 @@
public Map<String, File> getDynamicConfigFiles() {
Map<String, File> configMap = new HashMap<>();
for (String key : mBuildInfo.getBuildAttributes().keySet()) {
- if (key.startsWith(CONFIG_PATH_PREFIX)) {
- configMap.put(key.substring(CONFIG_PATH_PREFIX.length()),
+ if (key.startsWith(DynamicConfigHostSide.CONFIG_PATH_PREFIX)) {
+ configMap.put(key.substring(DynamicConfigHostSide.CONFIG_PATH_PREFIX.length()),
new File(mBuildInfo.getBuildAttributes().get(key)));
}
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
index c1f2d4b..7ac61c9 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
@@ -50,6 +50,7 @@
}
private static final String LOG_TAG = DynamicConfigPusher.class.getSimpleName();
+ private static final String TMP_FOLDER_DYNAMIC_FILES = "dynamic-config-files";
@Option(name = "cleanup", description = "Whether to remove config files from the test " +
"target after test completion.")
@@ -137,11 +138,14 @@
break;
case HOST:
- File storageDir = new File(DynamicConfig.CONFIG_FOLDER_ON_HOST);
- if (!storageDir.exists()) {
- storageDir.mkdir();
+ File storageDir = null;
+ try {
+ storageDir = FileUtil.createTempDir(TMP_FOLDER_DYNAMIC_FILES);
+ } catch (IOException e) {
+ throw new TargetSetupError("Fail to create a tmp folder for dynamic config "
+ + "files", e, device.getDeviceDescriptor());
}
- File hostDest = new File(DynamicConfig.CONFIG_FOLDER_ON_HOST + src.getName());
+ File hostDest = new File(storageDir, src.getName());
try {
FileUtil.copyFile(src, hostDest);
} catch (IOException e) {
@@ -149,7 +153,7 @@
src.getAbsolutePath(), hostDest.getAbsolutePath()), e,
device.getDeviceDescriptor());
}
- mFilePushed = hostDest.getAbsolutePath();
+ mFilePushed = storageDir.getAbsolutePath();
buildHelper.addDynamicConfigFile(mModuleName, src);
break;
}
@@ -169,9 +173,8 @@
}
break;
case HOST:
- if (mFilePushed != null) {
- FileUtil.deleteFile(new File(mFilePushed));
- }
+ FileUtil.recursiveDelete(new File(mFilePushed));
+ break;
}
}
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index f1e2b38..a10c3b5 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -55,6 +55,7 @@
import com.android.tradefed.util.AbiFormatter;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.ArrayUtil;
+import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.TimeUtil;
import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
@@ -64,7 +65,6 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -365,7 +365,12 @@
mLogcatOnFailure, mScreenshotOnFailure, mRebootOnFailure, mMaxLogcatBytes);
int moduleCount = modules.size();
if (moduleCount == 0) {
- CLog.logAndDisplay(LogLevel.INFO, "No module to run.");
+ CLog.logAndDisplay(LogLevel.INFO, "No module to run on %s.",
+ mDevice.getSerialNumber());
+ // Make sure we unlock other shards.
+ if (sPreparedLatch != null) {
+ sPreparedLatch.countDown();
+ }
return;
} else {
CLog.logAndDisplay(LogLevel.INFO, "Starting %d module%s on %s", moduleCount,
@@ -468,11 +473,7 @@
// was successful, and test execution should proceed to next module
ByteArrayOutputStream stack = new ByteArrayOutputStream();
due.printStackTrace(new PrintWriter(stack, true));
- try {
- stack.close();
- } catch (IOException ioe) {
- // won't happen on BAOS
- }
+ StreamUtil.close(stack);
CLog.w("Ignored DeviceUnresponsiveException because recovery was successful, "
+ "proceeding with next module. Stack trace: %s",
stack.toString());
@@ -740,7 +741,7 @@
mIncludeFilters.add(new TestFilter(mAbiName, module, mTestName).toString());
}
} catch (FileNotFoundException e) {
- e.printStackTrace();
+ throw new RuntimeException(e);
}
} else if (mTestName != null) {
throw new IllegalArgumentException(
@@ -805,6 +806,7 @@
mListCheckers = systemCheckers;
}
+ @Override
public void setCollectTestsOnly(boolean collectTestsOnly) {
mCollectTestsOnly = collectTestsOnly;
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
index e6605bf..edb241a 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
@@ -66,8 +66,7 @@
void setPreparerWhitelist(Set<String> preparerWhitelist);
/**
- * Push any necessary dynamic configuration, then run the module's precondition checks
- * and setup tasks.
+ * Pushes dynamic configuration, then runs the module's precondition checks and setup tasks.
* @param skipPrep whether preparation should be skipped
* @param preconditionArgs arguments to set on precondition preparers for the module, taking
* format arg-name:arg-value. If "arg-value" is unset, the value will default to "true".
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
index 33627b2..09d1219 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
@@ -222,35 +222,31 @@
// skip this name/abi combination.
continue;
}
- {
- Map<String, List<String>> args = new HashMap<>();
- if (mModuleArgs.containsKey(name)) {
- args.putAll(mModuleArgs.get(name));
- }
- if (mModuleArgs.containsKey(id)) {
- args.putAll(mModuleArgs.get(id));
- }
- if (args != null && args.size() > 0) {
- for (Entry<String, List<String>> entry : args.entrySet()) {
- for (String value : entry.getValue()) {
- // Collection-type options can be injected with multiple values
- config.injectOptionValue(entry.getKey(), value);
- }
- }
+
+ Map<String, List<String>> args = new HashMap<>();
+ if (mModuleArgs.containsKey(name)) {
+ args.putAll(mModuleArgs.get(name));
+ }
+ if (mModuleArgs.containsKey(id)) {
+ args.putAll(mModuleArgs.get(id));
+ }
+ for (Entry<String, List<String>> entry : args.entrySet()) {
+ for (String value : entry.getValue()) {
+ // Collection-type options can be injected with multiple values
+ config.injectOptionValue(entry.getKey(), value);
}
}
+
List<IRemoteTest> tests = config.getTests();
for (IRemoteTest test : tests) {
String className = test.getClass().getName();
- Map<String, List<String>> args = new HashMap<>();
+ Map<String, List<String>> testArgsMap = new HashMap<>();
if (mTestArgs.containsKey(className)) {
- args.putAll(mTestArgs.get(className));
+ testArgsMap.putAll(mTestArgs.get(className));
}
- if (args != null && args.size() > 0) {
- for (Entry<String, List<String>> entry : args.entrySet()) {
- for (String value : entry.getValue()) {
- config.injectOptionValue(entry.getKey(), value);
- }
+ for (Entry<String, List<String>> entry : testArgsMap.entrySet()) {
+ for (String value : entry.getValue()) {
+ config.injectOptionValue(entry.getKey(), value);
}
}
addFiltersToTest(test, abi, name);
@@ -490,6 +486,10 @@
if (res.isEmpty()) {
return null;
}
+ if (shardIndex >= res.size()) {
+ // If we could not shard up to expectation
+ return null;
+ }
return res.get(shardIndex);
}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestTest.java
index 415aa40..54307f8 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestTest.java
@@ -287,7 +287,7 @@
}
}, 0);
mTest.setDevice(mMockDevice);
- EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("FAKE_SERIAL");
+ EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("FAKE_SERIAL").times(2);
EasyMock.replay(mMockDevice, mMockListener);
mTest.run(mMockListener);
EasyMock.verify(mMockDevice, mMockListener);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
index 9793d8b..491255a 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
@@ -421,4 +421,20 @@
assertEquals(mod3, res.get(0));
assertEquals(mod4, res.get(1));
}
+
+ /**
+ * When reaching splitting time, we need to ensure that even after best effort, if we cannot
+ * split into the requested number of shardIndex, we simply return null to report an empty
+ * shard.
+ */
+ public void testGetShard_cannotSplitMore() {
+ List<IModuleDef> testList = new ArrayList<>();
+ TestRuntime test1 = new TestRuntime();
+ test1.runtimeHint = 100l;
+ IModuleDef mod1 = new ModuleDef("test1", new Abi("arm", "32"), test1,
+ new ArrayList<ITargetPreparer>());
+ testList.add(mod1);
+ List<IModuleDef> res = mRepo.getShard(testList, 1, 2);
+ assertNull(res);
+ }
}
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java
index 351c08f..8df1ebc 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java
@@ -16,6 +16,8 @@
package com.android.compatibility.common.util;
+import com.android.tradefed.util.FileUtil;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -35,8 +37,7 @@
public class DynamicConfigHandler {
- private static final String LOG_TAG = DynamicConfigHandler.class.getSimpleName();
-
+ private final static String MERGED_CONFIG_FILE_FOLDER = "dynamic-config-files-merged";
private static final String NS = null; //xml constant representing null namespace
private static final String ENCODING = "UTF-8";
@@ -75,9 +76,7 @@
private static File storeMergedConfigFile(Map<String, List<String>> configMap,
String moduleName) throws XmlPullParserException, IOException {
- File folder = new File(DynamicConfig.MERGED_CONFIG_FILE_FOLDER);
- folder.mkdirs();
-
+ File folder = FileUtil.createTempDir(MERGED_CONFIG_FILE_FOLDER);
File mergedConfigFile = new File(folder, moduleName + ".dynamic");
OutputStream stream = new FileOutputStream(mergedConfigFile);
XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java
index 3e94d36..fd823bf 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java
@@ -16,20 +16,54 @@
package com.android.compatibility.common.util;
+import com.android.tradefed.build.IBuildInfo;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
+import java.util.List;
+import java.util.Map;
/**
* Load dynamic config for device side test cases
*/
-public class DynamicConfigHostSide extends DynamicConfig {
+public class DynamicConfigHostSide {
- private static String LOG_TAG = DynamicConfigHostSide.class.getSimpleName();
+ public static final String CONFIG_PATH_PREFIX = "DYNAMIC_CONFIG_FILE:";
- public DynamicConfigHostSide(String moduleName) throws IOException, XmlPullParserException {
- File configFile = getConfigFile(new File(CONFIG_FOLDER_ON_HOST), moduleName);
- initializeConfig(configFile);
+ /**
+ * Returns the value of a key from a downloaded file.
+ *
+ * @param file The file downloaded, can be retrieve via
+ * {@link #getDynamicConfigFile(IBuildInfo, String)}
+ * @param key the key inside the file which value we want to return
+ * @return the value associated to the key in the config file provided.
+ */
+ public static String getValueFromConfig(File file, String key)
+ throws XmlPullParserException, IOException {
+ Map<String, List<String>> configMap = DynamicConfig.createConfigMap(file);
+ List<String> singleValue = configMap.get(key);
+ if (singleValue == null || singleValue.size() == 0 || singleValue.size() > 1) {
+ // key must exist in the map, and map to a list containing exactly one string
+ return null;
+ }
+ return singleValue.get(0);
+ }
+
+ /**
+ * Returns a dynamic config file downloaded in {@link DynamicConfigPusher} the path is stored
+ * in the build info under a module name.
+ *
+ * @param info the invocation {@link IBuildInfo}
+ * @param moduleName the name of the module of the file.
+ * @return a {@link File} created from the downloaded file.
+ */
+ public static File getDynamicConfigFile(IBuildInfo info, String moduleName) {
+ String path = info.getBuildAttributes().get(CONFIG_PATH_PREFIX + moduleName);
+ if (path!= null && !path.isEmpty()) {
+ return new File(path);
+ }
+ return null;
}
}
diff --git a/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java b/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java
index a1d3d0a2..31dd598 100644
--- a/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java
+++ b/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java
@@ -95,8 +95,9 @@
public void testDynamicConfigHandler() throws Exception {
String module = "test1";
File localConfigFile = createFileFromStr(localConfig, module);
+ File mergedFile = null;
try {
- File mergedFile = DynamicConfigHandler
+ mergedFile = DynamicConfigHandler
.getMergedDynamicConfigFile(localConfigFile, overrideJson, module);
Map<String, List<String>> configMap = DynamicConfig.createConfigMap(mergedFile);
@@ -117,6 +118,7 @@
.contains("override-config-list-val-2-1"));
} finally {
FileUtil.deleteFile(localConfigFile);
+ FileUtil.recursiveDelete(mergedFile.getParentFile());
}
}
diff --git a/common/util/src/com/android/compatibility/common/util/DynamicConfig.java b/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
index d316b9d..1efc5e1 100644
--- a/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
+++ b/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
@@ -42,12 +42,7 @@
public static final String ENTRY_TAG = "entry";
public static final String VALUE_TAG = "value";
public static final String KEY_ATTR = "key";
-
public final static String CONFIG_FOLDER_ON_DEVICE = "/sdcard/dynamic-config-files/";
- public final static String CONFIG_FOLDER_ON_HOST =
- System.getProperty("java.io.tmpdir") + "/dynamic-config-files/";
- public final static String MERGED_CONFIG_FILE_FOLDER =
- System.getProperty("java.io.tmpdir") + "/dynamic-config-files/merged";
protected Map<String, List<String>> mDynamicConfigMap = new HashMap<String, List<String>>();
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
index 2180c96..f6e56a8 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
@@ -217,9 +217,14 @@
if (featureList.contains("feature:android.hardware.type.watch\n") ||
featureList.contains("feature:android.hardware.type.television\n")) {
return false;
- } else {
- return true;
}
+ final String passwordActivity =
+ getDevice().executeShellCommand("cmd package resolve-activity --components "
+ + "-a android.app.action.SET_NEW_PASSWORD -c android.intent.category.DEFAULT");
+ if (passwordActivity.equals("No activity found\n")) {
+ return false;
+ }
+ return true;
}
private void waitForBootCompleted() throws Exception {
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index bc8f904..03af2d2 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -548,12 +548,9 @@
assertEquals("doc:web-linkable-file", DocumentsContract.getDocumentId(uri));
final ContentResolver resolver = getInstrumentation().getContext().getContentResolver();
- final String streamTypes[] = resolver.getStreamTypes(uri, "*/*");
- assertEquals(1, streamTypes.length);
- assertEquals("text/plain", streamTypes[0]);
Bundle bundle = new Bundle();
- bundle.putString(Intent.EXTRA_EMAIL, "x@x.com");
+ bundle.putStringArray(Intent.EXTRA_EMAIL, new String[] { "x@x.com" });
final IntentSender intentSender = DocumentsContract.createWebLinkIntent(resolver,
uri, bundle);
@@ -564,7 +561,7 @@
// Confirm the permissions dialog. The dialog is provided by the stub
// provider.
- UiObject okButton = new UiObject(new UiSelector().text("OK").clickable(true));
+ UiObject okButton = new UiObject(new UiSelector().resourceId("android:id/button1"));
assertNotNull(okButton);
assertTrue(okButton.waitForExists(TIMEOUT));
okButton.click();
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/ManagedProfileProvisioningTest.java b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/ManagedProfileProvisioningTest.java
index 553e446..cfba0a0 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/ManagedProfileProvisioningTest.java
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/ManagedProfileProvisioningTest.java
@@ -39,6 +39,7 @@
SilentProvisioningTestManager provisioningManager =
new SilentProvisioningTestManager(getContext());
assertTrue(provisioningManager.startProvisioningAndWait(intent));
+ assertTrue(isExtraUserPresent(provisioningManager.getReceviedProfileProvisionedIntent()));
}
// This is only necessary if the profile is created via managed provisioning flow.
@@ -47,4 +48,8 @@
getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.setProfileEnabled(AdminReceiver.getComponentName(getContext()));
}
+
+ private boolean isExtraUserPresent(Intent intent) {
+ return intent != null && intent.getExtras().containsKey(Intent.EXTRA_USER);
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml
index 335eadd..c43856e 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml
@@ -17,5 +17,6 @@
<reset-password />
<limit-password />
<disable-keyguard-features />
+ <disable-camera />
</uses-policies>
</device-admin>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PolicyTransparencyTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PolicyTransparencyTest.java
new file mode 100644
index 0000000..f8582ea
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PolicyTransparencyTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.deviceandprofileowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Log;
+
+/**
+ * Tests for {@link DevicePolicyManager#createAdminSupportIntent} API.
+ */
+public class PolicyTransparencyTest extends BaseDeviceAdminTest {
+
+ private static final String TAG = "PolicyTransparencyTest";
+
+ public void testCameraDisabled() throws Exception {
+ mDevicePolicyManager.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, true);
+
+ Intent intent = mDevicePolicyManager.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_CAMERA);
+ assertNotNull(intent);
+ assertEquals(ADMIN_RECEIVER_COMPONENT,
+ (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN));
+ assertEquals(DevicePolicyManager.POLICY_DISABLE_CAMERA,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+ mDevicePolicyManager.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, false);
+ intent = mDevicePolicyManager.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_CAMERA);
+ assertNull(intent);
+ }
+
+ public void testScreenCaptureDisabled() throws Exception {
+ mDevicePolicyManager.setScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT, true);
+
+ Intent intent = mDevicePolicyManager.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ assertNotNull(intent);
+ assertEquals(ADMIN_RECEIVER_COMPONENT,
+ (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN));
+ assertEquals(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+ mDevicePolicyManager.setScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT, false);
+ intent = mDevicePolicyManager.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ assertNull(intent);
+ }
+
+ public void testUserRestrictions() throws Exception {
+ // Test with a few random user restrictions:
+ runTestForRestriction(UserManager.DISALLOW_ADJUST_VOLUME);
+ runTestForRestriction(UserManager.DISALLOW_CONFIG_WIFI);
+ runTestForRestriction(UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
+ }
+
+ private void runTestForRestriction(String restriction) throws Exception {
+ mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT, restriction);
+
+ Intent intent = mDevicePolicyManager.createAdminSupportIntent(restriction);
+ assertNotNull(intent);
+ assertEquals(ADMIN_RECEIVER_COMPONENT,
+ (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN));
+ assertEquals(restriction, intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+ mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT, restriction);
+ intent = mDevicePolicyManager.createAdminSupportIntent(restriction);
+ assertNull(intent);
+ }
+}
+
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
index d39f96b..e4af64c 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
@@ -129,36 +129,6 @@
assertEquals(mUser, UserHandle.getUserHandleForUid(ai.uid));
}
- public void testAccessPrimaryProfileFromManagedProfile() throws Exception {
- // Try to access main profile from managed profile, which is not allowed.
- try {
- mLauncherApps.getActivityList(null, mUser);
- fail("Didn't throw SecurityException");
- } catch (SecurityException e) {
- assertTrue(e.getMessage().contains("another profile"));
- }
- try {
- mLauncherApps.getApplicationInfo(SIMPLE_APP_PACKAGE, /* flags= */ 0, mUser);
- fail("Didn't throw SecurityException");
- } catch (SecurityException e) {
- assertTrue(e.getMessage().contains("another profile"));
- }
- }
-
- public void testGetProfiles_fromMainProfile() {
- final List<UserHandle> profiles = mLauncherApps.getProfiles();
- assertEquals(2, profiles.size());
- assertTrue(profiles.contains(android.os.Process.myUserHandle()));
- assertEquals(getContext().getSystemService(UserManager.class).getUserProfiles(),
- profiles);
- }
-
- public void testGetProfiles_fromManagedProfile() {
- final List<UserHandle> profiles = mLauncherApps.getProfiles();
- assertEquals(1, profiles.size());
- assertEquals(android.os.Process.myUserHandle(), profiles.get(0));
- }
-
public void testPackageAddedCallbackForUser() throws Throwable {
int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_ADDED,
mUser, SIMPLE_APP_PACKAGE);
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index cbb637a..b8a5eae6 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -123,6 +123,15 @@
android:resource="@xml/authenticator" />
</service>
<activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"/>
+
+ <activity
+ android:name=".ProvisioningSuccessActivity"
+ android:theme="@android:style/Theme.NoDisplay">
+ <intent-filter>
+ <action android:name="android.app.action.PROVISIONING_SUCCESSFUL"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningSuccessActivity.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningSuccessActivity.java
new file mode 100644
index 0000000..cfce578
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningSuccessActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.managedprofile;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class ProvisioningSuccessActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ProvisioningTest.getSharedPreferences(this).edit()
+ .putBoolean(ProvisioningTest.KEY_PROVISIONING_SUCCESSFUL_RECEIVED, true).commit();
+ finish();
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java
index b31851a..6f8da31 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java
@@ -29,7 +29,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
import android.os.PersistableBundle;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -43,7 +42,8 @@
public class ProvisioningTest {
private static final String TAG = ProvisioningTest.class.getSimpleName();
- private static final String ADMIN_EXTRAS_BUNDLE_FILENAME = "admin_extras_bundle.txt";
+ private static final String SHARED_PREFERENCE_FILE_NAME = "shared-preferences-file-name";
+
private static final PersistableBundle ADMIN_EXTRAS_BUNDLE = new PersistableBundle();
private static final String ADMIN_EXTRAS_BUNDLE_KEY_1 = "KEY_1";
private static final String ADMIN_EXTRAS_BUNDLE_VALUE_1 = "VALUE_1";
@@ -51,6 +51,9 @@
ADMIN_EXTRAS_BUNDLE.putString(ADMIN_EXTRAS_BUNDLE_KEY_1, ADMIN_EXTRAS_BUNDLE_VALUE_1);
}
+ public static final String KEY_PROVISIONING_SUCCESSFUL_RECEIVED =
+ "key-provisioning-successful-received";
+
private static final ComponentName ADMIN_RECEIVER_COMPONENT = new ComponentName(
ProvisioningAdminReceiver.class.getPackage().getName(),
ProvisioningAdminReceiver.class.getName());
@@ -95,6 +98,12 @@
assertEquals(ADMIN_EXTRAS_BUNDLE_VALUE_1, bundle.getString(ADMIN_EXTRAS_BUNDLE_KEY_1));
}
+ @Test
+ public void testVerifySuccessfulIntentWasReceived() {
+ assertTrue(getSharedPreferences(mContext).getBoolean(KEY_PROVISIONING_SUCCESSFUL_RECEIVED,
+ false));
+ }
+
private void provisionManagedProfile() throws InterruptedException {
Intent intent = new Intent(ACTION_PROVISION_MANAGED_PROFILE)
.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, ADMIN_RECEIVER_COMPONENT)
@@ -112,21 +121,21 @@
return;
}
- getAdminExtraSharedPreferences(context).edit()
+ getSharedPreferences(context).edit()
.putString(ADMIN_EXTRAS_BUNDLE_KEY_1, bundle.getString(ADMIN_EXTRAS_BUNDLE_KEY_1))
.commit();
}
private static PersistableBundle loadBundle(Context context) {
- SharedPreferences pref = getAdminExtraSharedPreferences(context);
+ SharedPreferences pref = getSharedPreferences(context);
PersistableBundle bundle = new PersistableBundle();
bundle.putString(ADMIN_EXTRAS_BUNDLE_KEY_1,
pref.getString(ADMIN_EXTRAS_BUNDLE_KEY_1, null));
return bundle;
}
- private static SharedPreferences getAdminExtraSharedPreferences(Context context) {
- return context.getSharedPreferences(ADMIN_EXTRAS_BUNDLE_FILENAME, 0);
+ public static SharedPreferences getSharedPreferences(Context context) {
+ return context.getSharedPreferences(SHARED_PREFERENCE_FILE_NAME, 0);
}
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
index ed885c5..51b9d30 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
@@ -30,10 +30,10 @@
protected static final String LAUNCHER_TESTS_CLASS = LAUNCHER_TESTS_PKG + ".LauncherAppsTests";
protected static final String PARAM_TEST_USER = "testUser";
- protected static final String LAUNCHER_TESTS_APK = "CtsLauncherAppsTests.apk";
- protected static final String LAUNCHER_TESTS_SUPPORT_PKG =
+ private static final String LAUNCHER_TESTS_APK = "CtsLauncherAppsTests.apk";
+ private static final String LAUNCHER_TESTS_SUPPORT_PKG =
"com.android.cts.launchertests.support";
- protected static final String LAUNCHER_TESTS_SUPPORT_APK = "CtsLauncherAppsTestsSupport.apk";
+ private static final String LAUNCHER_TESTS_SUPPORT_APK = "CtsLauncherAppsTestsSupport.apk";
protected void installTestApps() throws Exception {
uninstallTestApps();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index c29b294..d4fe1f7 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -635,6 +635,13 @@
executeDeviceTestClass(".RequiredStrongAuthTimeoutTest");
}
+ public void testCreateAdminSupportIntent() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ executeDeviceTestClass(".PolicyTransparencyTest");
+ }
+
protected void executeDeviceTestClass(String className) throws Exception {
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusManagedProfileTest.java
index c37b2e2..fca45db 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusManagedProfileTest.java
@@ -355,7 +355,9 @@
addDisallowRemoveManagedProfileRestriction();
// Now we can't delete the managed profile any more to create a new one.
assertProvisionManagedProfileNotAllowed(COMP_DPC_PKG2);
- assertProvisionManagedProfileNotAllowed(COMP_DPC_PKG);
+ // But if it is initiated by the device owner, it is still possible, because the device
+ // owner itself has set the restriction
+ assertProvisionManagedProfileAllowed(COMP_DPC_PKG);
}
private void assertProvisionManagedProfileAllowed(String packageName) throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
index a978ccf..6521ed8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
@@ -43,6 +43,7 @@
mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
if (mHasFeature) {
removeTestUsers();
+ installTestApps();
// Create a managed profile
mParentUserId = mPrimaryUserId;
mProfileUserId = createManagedProfile(mParentUserId);
@@ -52,13 +53,6 @@
mProfileSerialNumber = Integer.toString(getUserSerialNumber(mProfileUserId));
mMainUserSerialNumber = Integer.toString(getUserSerialNumber(mParentUserId));
startUser(mProfileUserId);
-
- // Install test APK.
- installTestApps();
-
- // Also install on the managed profile too.
- installAppAsUser(LAUNCHER_TESTS_APK, mProfileUserId);
- installAppAsUser(LAUNCHER_TESTS_SUPPORT_APK, mProfileUserId);
}
}
@@ -81,29 +75,12 @@
// Run tests to check SimpleApp exists in both profile and main user.
runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
- LAUNCHER_TESTS_CLASS, "testSimpleAppInstalledForUser",
+ LAUNCHER_TESTS_CLASS,
+ "testSimpleAppInstalledForUser",
mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mProfileSerialNumber));
runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
- LAUNCHER_TESTS_CLASS, "testSimpleAppInstalledForUser",
+ LAUNCHER_TESTS_PKG + ".LauncherAppsTests", "testSimpleAppInstalledForUser",
mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mMainUserSerialNumber));
-
- // Run the same test on the managed profile. This still works.
- runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
- LAUNCHER_TESTS_CLASS, "testSimpleAppInstalledForUser",
- mProfileUserId, Collections.singletonMap(PARAM_TEST_USER, mProfileSerialNumber));
-
- // This tries to access main prorfile from work profiel, which is not allowed.
- runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
- LAUNCHER_TESTS_CLASS, "testAccessPrimaryProfileFromManagedProfile",
- mProfileUserId, Collections.singletonMap(PARAM_TEST_USER, mMainUserSerialNumber));
-
- // Test for getProfiles.
- runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
- LAUNCHER_TESTS_CLASS, "testGetProfiles_fromMainProfile",
- mParentUserId);
- runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
- LAUNCHER_TESTS_CLASS, "testGetProfiles_fromManagedProfile",
- mProfileUserId);
}
public void testLauncherCallbackPackageAddedProfile() throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
index 08a35d6..f11fe75 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
@@ -68,4 +68,13 @@
runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
"testVerifyAdminExtraBundle", mProfileUserId);
}
+
+ public void testVerifySuccessfulIntentWasReceived() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
+ "testVerifySuccessfulIntentWasReceived", mProfileUserId);
+ }
}
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
new file mode 100644
index 0000000..aecb401
--- /dev/null
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.dumpsys.cts;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.Set;
+
+public class BaseDumpsysTest extends DeviceTestCase implements IBuildReceiver {
+ protected static final String TAG = "DumpsysHostTest";
+
+ /**
+ * A reference to the device under test.
+ */
+ protected ITestDevice mDevice;
+
+ protected IBuildInfo mCtsBuild;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mDevice = getDevice();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ protected static long assertInteger(String input) {
+ try {
+ return Long.parseLong(input);
+ } catch (NumberFormatException e) {
+ fail("Expected an integer but found \"" + input + "\"");
+ // Won't be hit, above throws AssertException
+ return -1;
+ }
+ }
+
+ protected static double assertDouble(String input) {
+ try {
+ return Double.parseDouble(input);
+ } catch (NumberFormatException e) {
+ fail("Expected a double but found \"" + input + "\"");
+ return -1;
+ }
+ }
+
+ protected static void assertSeenTag(Set<String> seenTags, String tag) {
+ assertTrue("No line starting with \"" + tag + ",\"", seenTags.contains(tag));
+ }
+}
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTesst.java
similarity index 67%
rename from hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
rename to hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTesst.java
index acfc72d..cf06db2 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTesst.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,6 @@
package android.dumpsys.cts;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
import java.io.BufferedReader;
import java.io.File;
@@ -30,315 +26,13 @@
import java.util.Set;
/**
- * Test to check the format of the dumps of various services.
- * Currently procstats and batterystats are tested.
+ * Test to check the format of the dumps of the batterystats test.
*/
-public class DumpsysHostTest extends DeviceTestCase implements IBuildReceiver {
- private static final String TAG = "DumpsysHostTest";
- private static final String TEST_APK = "CtsFramestatsTestApp.apk";
+public class BatteryStatsDumpsysTesst extends BaseDumpsysTest {
+ private static final String TEST_APK = "CtsFramestatsTestApp.apk";
private static final String TEST_PKG = "com.android.cts.framestatstestapp";
/**
- * A reference to the device under test.
- */
- private ITestDevice mDevice;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mDevice = getDevice();
- }
-
- /**
- * Tests the output of "dumpsys procstats -c". This is a proxy for testing "dumpsys procstats
- * --checkin", since the latter is not idempotent.
- *
- * @throws Exception
- */
- public void testProcstatsOutput() throws Exception {
- String procstats = mDevice.executeShellCommand("dumpsys procstats -c");
- assertNotNull(procstats);
- assertTrue(procstats.length() > 0);
-
- Set<String> seenTags = new HashSet<>();
- int version = -1;
-
- try (BufferedReader reader = new BufferedReader(
- new StringReader(procstats))) {
-
- String line;
- while ((line = reader.readLine()) != null) {
- if (line.isEmpty()) {
- continue;
- }
-
- // extra space to make sure last column shows up.
- if (line.endsWith(",")) {
- line = line + " ";
- }
- String[] parts = line.split(",");
- seenTags.add(parts[0]);
-
- switch (parts[0]) {
- case "vers":
- assertEquals(2, parts.length);
- version = Integer.parseInt(parts[1]);
- break;
- case "period":
- checkPeriod(parts);
- break;
- case "pkgproc":
- checkPkgProc(parts, version);
- break;
- case "pkgpss":
- checkPkgPss(parts, version);
- break;
- case "pkgsvc-bound":
- case "pkgsvc-exec":
- case "pkgsvc-run":
- case "pkgsvc-start":
- checkPkgSvc(parts, version);
- break;
- case "pkgkills":
- checkPkgKills(parts, version);
- break;
- case "proc":
- checkProc(parts);
- break;
- case "pss":
- checkPss(parts);
- break;
- case "kills":
- checkKills(parts);
- break;
- case "total":
- checkTotal(parts);
- break;
- default:
- break;
- }
- }
- }
-
- // spot check a few tags
- assertSeenTag(seenTags, "pkgproc");
- assertSeenTag(seenTags, "proc");
- assertSeenTag(seenTags, "pss");
- assertSeenTag(seenTags, "total");
- }
-
- private void checkPeriod(String[] parts) {
- assertTrue("Expected 5 or 6, found: " + parts.length,
- parts.length == 5 || parts.length == 6);
- assertNotNull(parts[1]); // date
- assertInteger(parts[2]); // start time (msec)
- assertInteger(parts[3]); // end time (msec)
- assertNotNull(parts[4]); // status
- if (parts.length == 6) {
- assertNotNull(parts[5]); // swapped-out-pss
- }
- }
-
- private void checkPkgProc(String[] parts, int version) {
- int statesStartIndex;
-
- if (version < 4) {
- assertTrue(parts.length >= 4);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertNotNull(parts[3]); // process
- statesStartIndex = 4;
- } else {
- assertTrue(parts.length >= 5);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // app version
- assertNotNull(parts[4]); // process
- statesStartIndex = 5;
- }
-
- for (int i = statesStartIndex; i < parts.length; i++) {
- String[] subparts = parts[i].split(":");
- assertEquals(2, subparts.length);
- checkTag(subparts[0], true); // tag
- assertInteger(subparts[1]); // duration (msec)
- }
- }
-
- private void checkTag(String tag, boolean hasProcess) {
- assertEquals(hasProcess ? 3 : 2, tag.length());
-
- // screen: 0 = off, 1 = on
- char s = tag.charAt(0);
- if (s != '0' && s != '1') {
- fail("malformed tag: " + tag);
- }
-
- // memory: n = normal, m = moderate, l = low, c = critical
- char m = tag.charAt(1);
- if (m != 'n' && m != 'm' && m != 'l' && m != 'c') {
- fail("malformed tag: " + tag);
- }
-
- if (hasProcess) {
- char p = tag.charAt(2);
- assertTrue("malformed tag: " + tag, p >= 'a' && p <= 'z');
- }
- }
-
- private void checkPkgPss(String[] parts, int version) {
- int statesStartIndex;
-
- if (version < 4) {
- assertTrue(parts.length >= 4);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertNotNull(parts[3]); // process
- statesStartIndex = 4;
- } else {
- assertTrue(parts.length >= 5);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // app version
- assertNotNull(parts[4]); // process
- statesStartIndex = 5;
- }
-
- for (int i = statesStartIndex; i < parts.length; i++) {
- String[] subparts = parts[i].split(":");
- assertEquals(8, subparts.length);
- checkTag(subparts[0], true); // tag
- assertInteger(subparts[1]); // sample size
- assertInteger(subparts[2]); // pss min
- assertInteger(subparts[3]); // pss avg
- assertInteger(subparts[4]); // pss max
- assertInteger(subparts[5]); // uss min
- assertInteger(subparts[6]); // uss avg
- assertInteger(subparts[7]); // uss max
- }
- }
-
- private void checkPkgSvc(String[] parts, int version) {
- int statesStartIndex;
-
- if (version < 4) {
- assertTrue(parts.length >= 5);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertNotNull(parts[3]); // service name
- assertInteger(parts[4]); // count
- statesStartIndex = 5;
- } else {
- assertTrue(parts.length >= 6);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // app version
- assertNotNull(parts[4]); // service name
- assertInteger(parts[5]); // count
- statesStartIndex = 6;
- }
-
- for (int i = statesStartIndex; i < parts.length; i++) {
- String[] subparts = parts[i].split(":");
- assertEquals(2, subparts.length);
- checkTag(subparts[0], false); // tag
- assertInteger(subparts[1]); // duration (msec)
- }
- }
-
- private void checkPkgKills(String[] parts, int version) {
- String pssStr;
-
- if (version < 4) {
- assertEquals(8, parts.length);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertNotNull(parts[3]); // process
- assertInteger(parts[4]); // wakes
- assertInteger(parts[5]); // cpu
- assertInteger(parts[6]); // cached
- pssStr = parts[7];
- } else {
- assertEquals(9, parts.length);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // app version
- assertNotNull(parts[4]); // process
- assertInteger(parts[5]); // wakes
- assertInteger(parts[6]); // cpu
- assertInteger(parts[7]); // cached
- pssStr = parts[8];
- }
-
- String[] subparts = pssStr.split(":");
- assertEquals(3, subparts.length);
- assertInteger(subparts[0]); // pss min
- assertInteger(subparts[1]); // pss avg
- assertInteger(subparts[2]); // pss max
- }
-
- private void checkProc(String[] parts) {
- assertTrue(parts.length >= 3);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
-
- for (int i = 3; i < parts.length; i++) {
- String[] subparts = parts[i].split(":");
- assertEquals(2, subparts.length);
- checkTag(subparts[0], true); // tag
- assertInteger(subparts[1]); // duration (msec)
- }
- }
-
- private void checkPss(String[] parts) {
- assertTrue(parts.length >= 3);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
-
- for (int i = 3; i < parts.length; i++) {
- String[] subparts = parts[i].split(":");
- assertEquals(8, subparts.length);
- checkTag(subparts[0], true); // tag
- assertInteger(subparts[1]); // sample size
- assertInteger(subparts[2]); // pss min
- assertInteger(subparts[3]); // pss avg
- assertInteger(subparts[4]); // pss max
- assertInteger(subparts[5]); // uss min
- assertInteger(subparts[6]); // uss avg
- assertInteger(subparts[7]); // uss max
- }
- }
-
- private void checkKills(String[] parts) {
- assertEquals(7, parts.length);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // wakes
- assertInteger(parts[4]); // cpu
- assertInteger(parts[5]); // cached
- String pssStr = parts[6];
-
- String[] subparts = pssStr.split(":");
- assertEquals(3, subparts.length);
- assertInteger(subparts[0]); // pss min
- assertInteger(subparts[1]); // pss avg
- assertInteger(subparts[2]); // pss max
- }
-
- private void checkTotal(String[] parts) {
- assertTrue(parts.length >= 2);
- for (int i = 1; i < parts.length; i++) {
- String[] subparts = parts[i].split(":");
- checkTag(subparts[0], false); // tag
-
- if (subparts[1].contains("sysmemusage")) {
- break; // see b/18340771
- }
- assertInteger(subparts[1]); // duration (msec)
- }
- }
-
- /**
* Tests the output of "dumpsys batterystats --checkin".
*
* @throws Exception
@@ -948,37 +642,4 @@
}
assertTrue(foundAtLeastOneRow);
}
-
- private IBuildInfo mCtsBuild;
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
-
- private static long assertInteger(String input) {
- try {
- return Long.parseLong(input);
- } catch (NumberFormatException e) {
- fail("Expected an integer but found \"" + input + "\"");
- // Won't be hit, above throws AssertException
- return -1;
- }
- }
-
- private static double assertDouble(String input) {
- try {
- return Double.parseDouble(input);
- } catch (NumberFormatException e) {
- fail("Expected a double but found \"" + input + "\"");
- return -1;
- }
- }
-
- private static void assertSeenTag(Set<String> seenTags, String tag) {
- assertTrue("No line starting with \"" + tag + ",\"", seenTags.contains(tag));
- }
}
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
new file mode 100644
index 0000000..d78bf93
--- /dev/null
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.dumpsys.cts;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test to check the format of the dumps of the processstats test.
+ */
+public class ProcessStatsDumpsysTest extends BaseDumpsysTest {
+ /**
+ * Tests the output of "dumpsys procstats -c". This is a proxy for testing "dumpsys procstats
+ * --checkin", since the latter is not idempotent.
+ *
+ * @throws Exception
+ */
+ public void testProcstatsOutput() throws Exception {
+ String procstats = mDevice.executeShellCommand("dumpsys procstats -c");
+ assertNotNull(procstats);
+ assertTrue(procstats.length() > 0);
+
+ Set<String> seenTags = new HashSet<>();
+ int version = -1;
+
+ try (BufferedReader reader = new BufferedReader(
+ new StringReader(procstats))) {
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.isEmpty()) {
+ continue;
+ }
+
+ // extra space to make sure last column shows up.
+ if (line.endsWith(",")) {
+ line = line + " ";
+ }
+ String[] parts = line.split(",");
+ seenTags.add(parts[0]);
+
+ switch (parts[0]) {
+ case "vers":
+ assertEquals(2, parts.length);
+ version = Integer.parseInt(parts[1]);
+ break;
+ case "period":
+ checkPeriod(parts);
+ break;
+ case "pkgproc":
+ checkPkgProc(parts, version);
+ break;
+ case "pkgpss":
+ checkPkgPss(parts, version);
+ break;
+ case "pkgsvc-bound":
+ case "pkgsvc-exec":
+ case "pkgsvc-run":
+ case "pkgsvc-start":
+ checkPkgSvc(parts, version);
+ break;
+ case "pkgkills":
+ checkPkgKills(parts, version);
+ break;
+ case "proc":
+ checkProc(parts);
+ break;
+ case "pss":
+ checkPss(parts);
+ break;
+ case "kills":
+ checkKills(parts);
+ break;
+ case "total":
+ checkTotal(parts);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+/* more kinds
+total 0n:4426040 0m:5779 1n:6067104 1m:74489
+sysmemusage 0np1791668:791668:791668:40080:40080:40080:1176:1176:1176:147068:147068:147068:230682:230682:230682 ...
+weights 10573412 6.133572860728E12:40 ...
+availablepages Unmovable 0 69 37 31 14 20 28 16 5 7 0 0
+availablepages Reclaimable 0 0 0 0 1 1 1 1 0 0 1 0
+availablepages Movable 0 1 0 1 1 1 2 0 1 1 0 173
+availablepages Reserve 0 0 0 0 0 0 0 0 0 0 0 2
+availablepages CMA 0 1 1 98 111 70 35 20 7 9 3 18
+availablepages Isolate
+ */
+
+ // spot check a few tags
+ assertSeenTag(seenTags, "pkgproc");
+ assertSeenTag(seenTags, "proc");
+ assertSeenTag(seenTags, "pss");
+ assertSeenTag(seenTags, "total");
+ }
+
+ private void checkPeriod(String[] parts) {
+ assertTrue("Expected 5 or 6, found: " + parts.length,
+ parts.length == 5 || parts.length == 6);
+ assertNotNull(parts[1]); // date
+ assertInteger(parts[2]); // start time (msec)
+ assertInteger(parts[3]); // end time (msec)
+ assertNotNull(parts[4]); // status
+ if (parts.length == 6) {
+ assertNotNull(parts[5]); // swapped-out-pss
+ }
+ }
+
+ private void checkPkgProc(String[] parts, int version) {
+ int statesStartIndex;
+
+ if (version < 4) {
+ assertTrue(parts.length >= 4);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertNotNull(parts[3]); // process
+ statesStartIndex = 4;
+ } else {
+ assertTrue(parts.length >= 5);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // process
+ statesStartIndex = 5;
+ }
+
+ for (int i = statesStartIndex; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(2, subparts.length);
+ checkTag(subparts[0], true); // tag
+ assertInteger(subparts[1]); // duration (msec)
+ }
+ }
+
+ private void checkTag(String tag, boolean hasProcess) {
+ assertEquals(hasProcess ? 3 : 2, tag.length());
+
+ // screen: 0 = off, 1 = on
+ char s = tag.charAt(0);
+ if (s != '0' && s != '1') {
+ fail("malformed tag: " + tag);
+ }
+
+ // memory: n = normal, m = moderate, l = low, c = critical
+ char m = tag.charAt(1);
+ if (m != 'n' && m != 'm' && m != 'l' && m != 'c') {
+ fail("malformed tag: " + tag);
+ }
+
+ if (hasProcess) {
+ char p = tag.charAt(2);
+ assertTrue("malformed tag: " + tag, p >= 'a' && p <= 'z');
+ }
+ }
+
+ private void checkPkgPss(String[] parts, int version) {
+ int statesStartIndex;
+
+ if (version < 4) {
+ assertTrue(parts.length >= 4);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertNotNull(parts[3]); // process
+ statesStartIndex = 4;
+ } else {
+ assertTrue(parts.length >= 5);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // process
+ statesStartIndex = 5;
+ }
+
+ for (int i = statesStartIndex; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(8, subparts.length);
+ checkTag(subparts[0], true); // tag
+ assertInteger(subparts[1]); // sample size
+ assertInteger(subparts[2]); // pss min
+ assertInteger(subparts[3]); // pss avg
+ assertInteger(subparts[4]); // pss max
+ assertInteger(subparts[5]); // uss min
+ assertInteger(subparts[6]); // uss avg
+ assertInteger(subparts[7]); // uss max
+ }
+ }
+
+ private void checkPkgSvc(String[] parts, int version) {
+ int statesStartIndex;
+
+ if (version < 4) {
+ assertTrue(parts.length >= 5);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertNotNull(parts[3]); // service name
+ assertInteger(parts[4]); // count
+ statesStartIndex = 5;
+ } else {
+ assertTrue(parts.length >= 6);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // service name
+ assertInteger(parts[5]); // count
+ statesStartIndex = 6;
+ }
+
+ for (int i = statesStartIndex; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(2, subparts.length);
+ checkTag(subparts[0], false); // tag
+ assertInteger(subparts[1]); // duration (msec)
+ }
+ }
+
+ private void checkPkgKills(String[] parts, int version) {
+ String pssStr;
+
+ if (version < 4) {
+ assertEquals(8, parts.length);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertNotNull(parts[3]); // process
+ assertInteger(parts[4]); // wakes
+ assertInteger(parts[5]); // cpu
+ assertInteger(parts[6]); // cached
+ pssStr = parts[7];
+ } else {
+ assertEquals(9, parts.length);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // process
+ assertInteger(parts[5]); // wakes
+ assertInteger(parts[6]); // cpu
+ assertInteger(parts[7]); // cached
+ pssStr = parts[8];
+ }
+
+ String[] subparts = pssStr.split(":");
+ assertEquals(3, subparts.length);
+ assertInteger(subparts[0]); // pss min
+ assertInteger(subparts[1]); // pss avg
+ assertInteger(subparts[2]); // pss max
+ }
+
+ private void checkProc(String[] parts) {
+ assertTrue(parts.length >= 3);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+
+ for (int i = 3; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(2, subparts.length);
+ checkTag(subparts[0], true); // tag
+ assertInteger(subparts[1]); // duration (msec)
+ }
+ }
+
+ private void checkPss(String[] parts) {
+ assertTrue(parts.length >= 3);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+
+ for (int i = 3; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(8, subparts.length);
+ checkTag(subparts[0], true); // tag
+ assertInteger(subparts[1]); // sample size
+ assertInteger(subparts[2]); // pss min
+ assertInteger(subparts[3]); // pss avg
+ assertInteger(subparts[4]); // pss max
+ assertInteger(subparts[5]); // uss min
+ assertInteger(subparts[6]); // uss avg
+ assertInteger(subparts[7]); // uss max
+ }
+ }
+
+ private void checkKills(String[] parts) {
+ assertEquals(7, parts.length);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // wakes
+ assertInteger(parts[4]); // cpu
+ assertInteger(parts[5]); // cached
+ String pssStr = parts[6];
+
+ String[] subparts = pssStr.split(":");
+ assertEquals(3, subparts.length);
+ assertInteger(subparts[0]); // pss min
+ assertInteger(subparts[1]); // pss avg
+ assertInteger(subparts[2]); // pss max
+ }
+
+ private void checkTotal(String[] parts) {
+ assertTrue(parts.length >= 2);
+ for (int i = 1; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ checkTag(subparts[0], false); // tag
+
+ if (subparts[1].contains("sysmemusage")) {
+ break; // see b/18340771
+ }
+ assertInteger(subparts[1]); // duration (msec)
+ }
+ }
+
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
index 7ca4a5a..48fcca0 100644
--- a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
@@ -15,10 +15,22 @@
*/
package com.android.server.cts;
+import android.service.NetworkIdentityProto;
+import android.service.NetworkInterfaceProto;
+import android.service.NetworkStatsCollectionKeyProto;
+import android.service.NetworkStatsCollectionStatsProto;
+import android.service.NetworkStatsHistoryBucketProto;
+import android.service.NetworkStatsHistoryProto;
+import android.service.NetworkStatsRecorderProto;
import android.service.NetworkStatsServiceDumpProto;
+import java.util.List;
+
/**
* Test for "dumpsys netstats --proto"
+ *
+ * Note most of the logic here is just heuristics.
+ *
* Usage:
cts-tradefed run cts --skip-device-info --skip-preconditions \
@@ -56,6 +68,159 @@
final NetworkStatsServiceDumpProto dump = getDump(NetworkStatsServiceDumpProto.parser(),
"dumpsys netstats --proto");
- // TODO Actually check the output.
+ checkInterfaces(dump.getActiveInterfacesList());
+ checkInterfaces(dump.getActiveUidInterfacesList());
+
+ checkStats(dump.getDevStats(), /*withUid=*/ false, /*withTag=*/ false);
+ checkStats(dump.getXtStats(), /*withUid=*/ false, /*withTag=*/ false);
+ checkStats(dump.getUidStats(), /*withUid=*/ true, /*withTag=*/ false);
+ checkStats(dump.getUidTagStats(), /*withUid=*/ true, /*withTag=*/ true);
+ }
+
+ private void assertPositive(String name, long value) {
+ if (value > 0) return;
+ fail(name + " expected to be positive, but was: " + value);
+ }
+
+ private void assertNotNegative(String name, long value) {
+ if (value >= 0) return;
+ fail(name + " expected to be zero or positive, but was: " + value);
+ }
+
+ private void checkInterfaces(List<NetworkInterfaceProto> interfaces) {
+ /* Example:
+ active_interfaces=[
+ NetworkInterfaceProto {
+ interface=wlan0
+ identities=NetworkIdentitySetProto {
+ identities=[
+ NetworkIdentityProto {
+ type=1
+ subscriber_id=
+ network_id="wifiap"
+ roaming=false
+ metered=false
+ }
+ ]
+ }
+ }
+ ]
+ */
+ assertTrue("There must be at least one network device",
+ interfaces.size() > 0);
+
+ boolean allRoaming = true;
+ boolean allMetered = true;
+
+ for (NetworkInterfaceProto iface : interfaces) {
+ assertTrue("Missing interface name", !iface.getInterface().isEmpty());
+
+ assertPositive("# identities", iface.getIdentities().getIdentitiesList().size());
+
+ for (NetworkIdentityProto iden : iface.getIdentities().getIdentitiesList()) {
+ allRoaming &= iden.getRoaming();
+ allMetered &= iden.getMetered();
+
+ // TODO Can we check the other fields too? type, subscriber_id, and network_id.
+ }
+ }
+ assertFalse("There must be at least one non-roaming interface during CTS", allRoaming);
+ assertFalse("There must be at least one non-metered interface during CTS", allMetered);
+ }
+
+ private void checkStats(NetworkStatsRecorderProto record, boolean withUid, boolean withTag) {
+ /*
+ * Example:
+ dev_stats=NetworkStatsRecorderProto {
+ pending_total_bytes=136
+ complete_history=NetworkStatsCollectionProto {
+ stats=[
+ NetworkStatsCollectionStatsProto {
+ key=NetworkStatsCollectionKeyProto {
+ identity=NetworkIdentitySetProto {
+ identities=[
+ NetworkIdentityProto {
+ type=1
+ subscriber_id=
+ network_id="wifiap"
+ roaming=false
+ metered=false
+ }
+ ]
+ }
+ uid=-1
+ set=-1
+ tag=0
+ }
+ history=NetworkStatsHistoryProto {
+ bucket_duration_ms=3600000
+ buckets=[
+ NetworkStatsHistoryBucketProto {
+ bucket_start_ms=2273694336
+ rx_bytes=2142
+ rx_packets=10
+ tx_bytes=1568
+ tx_packets=12
+ operations=0
+ }
+ NetworkStatsHistoryBucketProto {
+ bucket_start_ms=3196682880
+ rx_bytes=2092039
+ rx_packets=1987
+ tx_bytes=236735
+ tx_packets=1750
+ operations=0
+ }
+ */
+
+ assertNotNegative("Pending bytes", record.getPendingTotalBytes());
+
+ for (NetworkStatsCollectionStatsProto stats : record.getCompleteHistory().getStatsList()) {
+
+ final NetworkStatsCollectionKeyProto key = stats.getKey();
+
+ // TODO Check the key.
+
+ final NetworkStatsHistoryProto hist = stats.getHistory();
+
+ assertPositive("duration", hist.getBucketDurationMs());
+
+ // Subtract one hour from duration to compensate for possible DTS.
+ final long minInterval = hist.getBucketDurationMs() - (60 * 60 * 1000);
+
+ NetworkStatsHistoryBucketProto prev = null;
+ for (NetworkStatsHistoryBucketProto bucket : hist.getBucketsList()) {
+
+ // Make sure the start time is increasing by at least the "duration",
+ // except we subtract duration from one our to compensate possible DTS.
+
+ if (prev != null) {
+ assertTrue(
+ String.format("Last start=%d, current start=%d, diff=%d, duration=%d",
+ prev.getBucketStartMs(), bucket.getBucketStartMs(),
+ (bucket.getBucketStartMs() - prev.getBucketStartMs()),
+ minInterval),
+ (bucket.getBucketStartMs() - prev.getBucketStartMs()) >=
+ minInterval);
+ }
+ assertNotNegative("RX bytes", bucket.getRxBytes());
+ assertNotNegative("RX packets", bucket.getRxPackets());
+ assertNotNegative("TX bytes", bucket.getTxBytes());
+ assertNotNegative("TX packets", bucket.getTxPackets());
+
+ // It should be safe to say # of bytes >= 10 * 10 of packets, due to headers, etc...
+ final long FACTOR = 10;
+ assertTrue(
+ String.format("# of bytes %d too small for # of packets %d",
+ bucket.getRxBytes(), bucket.getRxPackets()),
+ bucket.getRxBytes() >= bucket.getRxPackets() * FACTOR);
+ assertTrue(
+ String.format("# of bytes %d too small for # of packets %d",
+ bucket.getTxBytes(), bucket.getTxPackets()),
+ bucket.getTxBytes() >= bucket.getTxPackets() * FACTOR);
+ }
+ }
+
+ // TODO Make sure test app's UID actually shows up.
}
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
index de32000..1c4050c 100755
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
@@ -65,7 +65,7 @@
android:taskAffinity="nobody.but.LaunchToSideActivity"
/>
<activity android:name=".PipActivity"
- android:resizeableActivity="true"
+ android:resizeableActivity="false"
android:supportsPictureInPicture="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true"
@@ -73,41 +73,41 @@
/>
<activity android:name=".AlwaysFocusablePipActivity"
android:theme="@style/Theme.Transparent"
- android:resizeableActivity="true"
+ android:resizeableActivity="false"
android:supportsPictureInPicture="true"
androidprv:alwaysFocusable="true"
android:exported="true"
android:taskAffinity="nobody.but.AlwaysFocusablePipActivity"
/>
<activity android:name=".LaunchIntoPinnedStackPipActivity"
- android:resizeableActivity="true"
+ android:resizeableActivity="false"
android:supportsPictureInPicture="true"
androidprv:alwaysFocusable="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true"
/>
<activity android:name=".VisibleBehindActivity"
- android:resizeableActivity="true"
+ android:resizeableActivity="false"
android:supportsPictureInPicture="true"
android:exported="true"
android:taskAffinity="nobody.but.VisibleBehindActivity"
/>
<activity android:name=".LaunchPipOnPipActivity"
- android:resizeableActivity="true"
+ android:resizeableActivity="false"
android:supportsPictureInPicture="true"
android:taskAffinity="nobody.but.LaunchPipOnPipActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true"
/>
<activity android:name=".LaunchTapToFinishPipActivity"
- android:resizeableActivity="true"
+ android:resizeableActivity="false"
android:supportsPictureInPicture="true"
androidprv:alwaysFocusable="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true"
/>
<activity android:name=".LaunchImeWithPipActivity"
- android:resizeableActivity="true"
+ android:resizeableActivity="false"
android:supportsPictureInPicture="true"
androidprv:alwaysFocusable="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
@@ -115,7 +115,7 @@
android:windowSoftInputMode="stateAlwaysVisible"
/>
<activity android:name=".PipOnStopActivity"
- android:resizeableActivity="true"
+ android:resizeableActivity="false"
android:supportsPictureInPicture="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true"
@@ -144,7 +144,7 @@
<layout android:defaultWidth="25%"
android:defaultHeight="35%"
android:gravity="top|right"
- android:minWidth="100dp"
+ android:minWidth="90dp"
android:minHeight="80dp"
/>
</activity>
@@ -155,7 +155,7 @@
<layout android:defaultWidth="25%"
android:defaultHeight="35%"
android:gravity="bottom|left"
- android:minWidth="100dp"
+ android:minWidth="90dp"
android:minHeight="80dp"
/>
</activity>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
index 855b374..e6be28c 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
@@ -52,6 +52,11 @@
private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
+ private static final int APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
+ private static final int APP_OPS_MODE_ALLOWED = 0;
+ private static final int APP_OPS_MODE_IGNORED = 1;
+ private static final int APP_OPS_MODE_ERRORED = 2;
+
private static final int ROTATION_0 = 0;
private static final int ROTATION_90 = 1;
private static final int ROTATION_180 = 2;
@@ -481,6 +486,24 @@
assertPinnedStackIsOnTop();
}
+ public void testAppOpsDenyPipOnPause() throws Exception {
+ // Disable enter-pip-on-hide and try to enter pip
+ setAppOpsOpToMode(ActivityManagerTestBase.componentName,
+ APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE, APP_OPS_MODE_IGNORED);
+
+ // Launch the PIP activity on pause
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+ assertPinnedStackDoesNotExist();
+
+ // Go home and ensure that there is no pinned stack
+ launchHomeActivity();
+ assertPinnedStackDoesNotExist();
+
+ // Re-enable enter-pip-on-hide
+ setAppOpsOpToMode(ActivityManagerTestBase.componentName,
+ APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE, APP_OPS_MODE_ALLOWED);
+ }
+
/**
* Asserts that the pinned stack bounds does not intersect with the IME bounds.
*/
@@ -567,6 +590,13 @@
executeShellCommand(String.format("input tap %d %d", tapX, tapY));
}
+ /**
+ * Sets an app-ops op for a given package to a given mode.
+ */
+ private void setAppOpsOpToMode(String packageName, int op, int mode) throws Exception {
+ executeShellCommand(String.format("appops set %s %d %d", packageName, op, mode));
+ }
+
private void pinnedStackTester(String startActivityCmd, String topActivityName,
boolean moveTopToPinnedStack, boolean isFocusable) throws Exception {
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java
index 4de7848..0c5e662 100644
--- a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java
@@ -59,75 +59,38 @@
new ComponentName(getContext(), MainActivity.class))).build())));
}
- public void test04_getAndLaunch_primary() {
- Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
-
- final UserHandle userCurrent = android.os.Process.myUserHandle();
- final UserHandle userOther = getManagedUser();
-
- final ShortcutQuery q = new ShortcutQuery()
- .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC)
- .setPackage(getContext().getPackageName())
- .setShortcutIds(list("s1"));
- assertWith(getLauncherApps().getShortcuts(q, userCurrent))
- .haveIds("s1")
- .areAllDynamic()
- .forShortcutWithId("s1", si -> {
- assertEquals("label1", si.getShortLabel());
- assertEquals(userCurrent, si.getUserHandle());
- });
- assertWith(getLauncherApps().getShortcuts(q, userOther))
- .haveIds("s1")
- .areAllDynamic()
- .forShortcutWithId("s1", si -> {
- assertEquals("label2", si.getShortLabel());
- assertEquals(userOther, si.getUserHandle());
- });
-
- // Just call start and make sure they don't throw.
- getLauncherApps().startShortcut(getContext().getPackageName(), "s1", null, null,
- userCurrent);
-
- // TODO Make sure the activity actually starts.
- getLauncherApps().startShortcut(getContext().getPackageName(), "s1", null, null,
- userOther);
- }
-
- public void test04_getAndLaunch_managed() {
+ public void test04_getAndLaunch() {
Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
final UserHandle userMain = android.os.Process.myUserHandle();
- final UserHandle userOther = getManagedUser();
+ final UserHandle userManaged = getManagedUser();
final ShortcutQuery q = new ShortcutQuery()
.setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC)
.setPackage(getContext().getPackageName())
.setShortcutIds(list("s1"));
- try {
- getLauncherApps().getShortcuts(q, userOther);
- fail("Didn't throw SecurityException");
- } catch (SecurityException e) {
- assertTrue(e.getMessage().contains("another profile"));
- }
assertWith(getLauncherApps().getShortcuts(q, userMain))
.haveIds("s1")
.areAllDynamic()
.forShortcutWithId("s1", si -> {
- assertEquals("label2", si.getShortLabel());
+ assertEquals("label1", si.getShortLabel());
assertEquals(userMain, si.getUserHandle());
});
+ assertWith(getLauncherApps().getShortcuts(q, userManaged))
+ .haveIds("s1")
+ .areAllDynamic()
+ .forShortcutWithId("s1", si -> {
+ assertEquals("label2", si.getShortLabel());
+ assertEquals(userManaged, si.getUserHandle());
+ });
- try {
- getLauncherApps().startShortcut(getContext().getPackageName(), "s1", null, null,
- userOther);
- fail("Didn't throw SecurityException");
- } catch (SecurityException e) {
- assertTrue(e.getMessage().contains("another profile"));
- }
+ // Just call start and make sure they don't throw.
+ getLauncherApps().startShortcut(getContext().getPackageName(), "s1", null, null,
+ userMain);
// TODO Make sure the activity actually starts.
getLauncherApps().startShortcut(getContext().getPackageName(), "s1", null, null,
- userMain);
+ userManaged);
}
private UserHandle getManagedUser() {
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
index c5a97e7..a74ab45 100644
--- a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
@@ -51,11 +51,8 @@
"test02_createShortuctsOnPrimaryUser", getPrimaryUserId());
runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
"test03_createShortuctsOnManagedProfile", profileId);
-
runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
- "test04_getAndLaunch_primary", getPrimaryUserId());
- runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
- "test04_getAndLaunch_managed", profileId);
+ "test04_getAndLaunch", getPrimaryUserId());
}
public void testSecondaryUser() throws Exception {
diff --git a/hostsidetests/tv/src/com/android/cts/tv/TvInputManagerHostTest.java b/hostsidetests/tv/src/com/android/cts/tv/TvInputManagerHostTest.java
index 67cd752..3cb6a0ec 100644
--- a/hostsidetests/tv/src/com/android/cts/tv/TvInputManagerHostTest.java
+++ b/hostsidetests/tv/src/com/android/cts/tv/TvInputManagerHostTest.java
@@ -17,7 +17,6 @@
package com.android.cts.tv;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.util.VersionCodes;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index 454a2ab..f2e0a7b 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -822,4 +822,16 @@
assertDeviceOwnerMessage(e.getMessage());
}
}
+
+ public void testCreateAdminSupportIntent_returnNullIfRestrictionIsNotSet() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testCreateAdminSupportIntent");
+ return;
+ }
+ Intent intent = mDevicePolicyManager.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_CAMERA);
+ assertNull(intent);
+ intent = mDevicePolicyManager.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+ assertNull(intent);
+ }
}
diff --git a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
new file mode 100644
index 0000000..9d8f0111
--- /dev/null
+++ b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.cts;
+
+import android.app.NotificationChannelGroup;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+public class NotificationChannelGroupTest extends AndroidTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public void testDescribeContents() {
+ final int expected = 0;
+ NotificationChannelGroup group = new NotificationChannelGroup("1", "1");
+ assertEquals(expected, group.describeContents());
+ }
+
+ public void testConstructor() {
+ NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
+ assertEquals("1", group.getId());
+ assertEquals("one", group.getName());
+ }
+
+ public void testWriteToParcel() {
+ NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
+ Parcel parcel = Parcel.obtain();
+ group.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ NotificationChannelGroup fromParcel =
+ NotificationChannelGroup.CREATOR.createFromParcel(parcel);
+ assertEquals(group, fromParcel);
+ }
+}
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 232abb8..d16bffb 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -18,6 +18,7 @@
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.stubs.R;
@@ -72,7 +73,7 @@
try {
mNotificationManager.createNotificationChannel(channel);
final NotificationChannel createdChannel =
- mNotificationManager.getNotificationChannel("id");
+ mNotificationManager.getNotificationChannel(mId);
compareChannels(channel, createdChannel);
// Lockscreen Visibility and canBypassDnd no longer settable.
assertTrue(createdChannel.getLockscreenVisibility() != Notification.VISIBILITY_SECRET);
@@ -112,6 +113,34 @@
}
}
+ public void testCreateChannelWithGroup() {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("g", "n");
+ mNotificationManager.createNotificationChannelGroup(ncg);
+ NotificationChannel channel =
+ new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setGroup(ncg.getId());
+ try {
+ mNotificationManager.createNotificationChannel(channel);
+ compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
+ } finally {
+ mNotificationManager.deleteNotificationChannel(channel.getId());
+ }
+ }
+
+ public void testCreateChannelWithBadGroup() {
+ NotificationChannel channel =
+ new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setGroup("garbage");
+ try {
+ try {
+ mNotificationManager.createNotificationChannel(channel);
+ fail("Created notification with bad group");
+ } catch (IllegalArgumentException e) {}
+ } finally {
+ mNotificationManager.deleteNotificationChannel(channel.getId());
+ }
+ }
+
public void testCreateChannelInvalidImportance() {
NotificationChannel channel =
new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_UNSPECIFIED);
@@ -284,6 +313,31 @@
assertTrue("notification list was not empty after cancelAll", sbns.length == 0);
}
+ public void testNotifyWithTimeout() {
+ mNotificationManager.cancelAll();
+ final int id = 128;
+ final long timeout = 1000;
+
+ final Notification notification = new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.black)
+ .setContentTitle("notify#" + id)
+ .setContentText("This is #" + id + "notification ")
+ .setTimeout(System.currentTimeMillis() + timeout)
+ .build();
+ mNotificationManager.notify(id, notification);
+
+ if (!checkNotificationExistence(id, /*shouldExist=*/ true)) {
+ fail("couldn't find posted notification id=" + id);
+ }
+
+ try {
+ Thread.sleep(timeout);
+ } catch (InterruptedException ex) {
+ // pass
+ }
+ checkNotificationExistence(id, false);
+ }
+
private void sendNotification(final int id, final int icon) {
final Intent intent = new Intent(Intent.ACTION_MAIN, Threads.CONTENT_URI);
@@ -301,19 +355,31 @@
.build();
mNotificationManager.notify(id, notification);
-
if (!checkNotificationExistence(id, /*shouldExist=*/ true)) {
fail("couldn't find posted notification id=" + id);
}
}
private boolean checkNotificationExistence(int id, boolean shouldExist) {
+ // notification is a bit asynchronous so it may take a few ms to appear in
+ // getActiveNotifications()
+ // we will check for it for up to 200ms before giving up
boolean found = false;
- final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
- for (StatusBarNotification sbn : sbns) {
- if (sbn.getId() == id) {
- found = true;
- break;
+ for (int tries=3; tries-->0;) {
+ // Need reset flag.
+ found = false;
+ final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
+ for (StatusBarNotification sbn : sbns) {
+ if (sbn.getId() == id) {
+ found = true;
+ break;
+ }
+ }
+ if (found == shouldExist) break;
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ // pass
}
}
return found == shouldExist;
@@ -335,5 +401,6 @@
assertEquals(expected.getImportance(), actual.getImportance());
assertEquals(expected.getSound(), actual.getSound());
assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
+ assertEquals(expected.getGroup(), actual.getGroup());
}
}
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 42a158e..0988c15 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -27,6 +27,7 @@
public class NotificationTest extends AndroidTestCase {
+ private Notification.Action mAction;
private Notification mNotification;
private Context mContext;
@@ -34,6 +35,7 @@
private static final String CONTENT_TITLE = "contentTitle";
private static final String CONTENT_TEXT = "contentText";
private static final String URI_STRING = "uriString";
+ private static final String ACTION_TITLE = "actionTitle";
private static final int TOLERANCE = 200;
@Override
@@ -150,6 +152,16 @@
assertNull(result.sound);
}
+ public void testColorizeNotification() {
+ mNotification = new Notification.Builder(mContext)
+ .setSmallIcon(1)
+ .setContentTitle(CONTENT_TITLE)
+ .setColorized(true)
+ .build();
+
+ assertTrue(mNotification.extras.getBoolean(Notification.EXTRA_COLORIZED));
+ }
+
public void testBuilder() {
final Intent intent = new Intent();
final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
@@ -165,6 +177,16 @@
assertEquals(contentIntent, mNotification.contentIntent);
}
+ public void testActionBuilder() {
+ final Intent intent = new Intent();
+ final PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ mAction = null;
+ mAction = new Notification.Action.Builder(0, ACTION_TITLE, actionIntent).build();
+ assertEquals(ACTION_TITLE, mAction.title);
+ assertEquals(actionIntent, mAction.actionIntent);
+ assertEquals(true, mAction.getAllowGeneratedReplies());
+ }
+
public void testToString() {
mNotification = new Notification();
assertNotNull(mNotification.toString());
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 0b6f235..8a90892 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -26,6 +26,7 @@
import android.support.test.runner.AndroidJUnit4;
import org.junit.AfterClass;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
@@ -42,7 +43,7 @@
protected static UiBot sUiBot;
@BeforeClass
- public static void setUiBot() {
+ public static void setUiBot() throws Exception {
sUiBot = new UiBot(InstrumentationRegistry.getInstrumentation(), UI_TIMEOUT_SEC);
}
@@ -52,6 +53,11 @@
assertServiceDisabled();
}
+ @Before
+ public void resetServiceState() {
+ InstrumentedAutoFillService.resetNumberFillRequests();
+ }
+
/**
* Enables the {@link InstrumentedAutoFillService} for auto-fill for the default user.
*/
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index 2237002..c1ff5eb 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -34,6 +34,7 @@
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -46,6 +47,8 @@
private static final AtomicReference<CannedFillResponse> sCannedFillResponse =
new AtomicReference<>();
+ private static AtomicInteger sNumberFillRequests = new AtomicInteger(0);
+
// TODO(b/33197203, b/33802548): add tests for onConnected() / onDisconnected() and/or remove
// overriden methods below that are only logging their calls.
@@ -62,8 +65,9 @@
@Override
public void onFillRequest(AssistStructure structure, Bundle data,
CancellationSignal cancellationSignal, FillCallback callback) {
+ final int requestNumber = sNumberFillRequests.incrementAndGet();
final CannedFillResponse cannedResponse = sCannedFillResponse.getAndSet(null);
- Log.v(TAG, "onFillRequest(): cannedResponse = " + cannedResponse);
+ Log.v(TAG, "onFillRequest(#" + requestNumber + "): cannedResponse = " + cannedResponse);
if (cannedResponse == null) {
callback.onSuccess(null);
@@ -120,6 +124,24 @@
}
}
+ /**
+ * Resets the number of requests to {@link #onFillRequest(AssistStructure, Bundle,
+ * CancellationSignal, FillCallback)} so it can be verified by
+ * {@link #assertNumberFillRequests(int)}.
+ */
+ static void resetNumberFillRequests() {
+ sNumberFillRequests.set(0);
+ }
+
+ /**
+ * Asserts the number of calls to {@link #onFillRequest(AssistStructure, Bundle,
+ * CancellationSignal, FillCallback)} since the last call to {@link #resetNumberFillRequests()}.
+ */
+ static void assertNumberFillRequests(int expected) {
+ final int actual = sNumberFillRequests.get();
+ assertWithMessage("Invalid number of fill requests").that(actual).isEqualTo(expected);
+ }
+
private void fill(Dataset.Builder builder, Map<String, AutoFillValue> fields,
ViewNode view) {
final String resourceId = view.getIdEntry();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index 86a38b7..9d94c99 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -50,6 +50,9 @@
private static final String TAG = "LoginActivity";
+ static final String ID_USERNAME = "username";
+ static final String ID_PASSWORD = "password";
+
private EditText mUsernameEditText;
private EditText mPasswordEditText;
private TextView mOutput;
@@ -117,8 +120,8 @@
*/
void expectAutoFill(CannedDataset.Builder builder, String username, String password) {
builder
- .setField("username", AutoFillValue.forText(username))
- .setField("password", AutoFillValue.forText(password));
+ .setField(ID_USERNAME, AutoFillValue.forText(username))
+ .setField(ID_PASSWORD, AutoFillValue.forText(password));
mAutoFillExpectation = new AutoFillExpectation(username, password);
mUsernameEditText
.addTextChangedListener(new MyTextWatcher(mAutoFillExpectation.usernameLatch));
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 43ed714..ba8ff95 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -15,6 +15,10 @@
*/
package android.autofillservice.cts;
+import static android.autofillservice.cts.LoginActivity.ID_PASSWORD;
+import static android.autofillservice.cts.LoginActivity.ID_USERNAME;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
@@ -22,8 +26,6 @@
import org.junit.Rule;
import org.junit.Test;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-
@SmallTest
public class LoginActivityTest extends AutoFillServiceTestCase {
@@ -49,10 +51,22 @@
.addDataset(dataset.build())
.build());
- sUiBot.triggerFillRequest();
+ sUiBot.triggerImeByRelativeId(ID_USERNAME);
+
+ // TODO(b/33197203, b/33802548): temporary hack because UI is based on notifications
+ sUiBot.collapseStatusBar();
+
+ // Make sure tapping on other fields from the dataset does not trigger it again
+ sUiBot.tapByRelativeId(ID_PASSWORD);
+ sUiBot.tapByRelativeId(ID_USERNAME);
+
+ // TODO(b/33197203, b/33802548): temporary hack because UI is based on notifications
+ sUiBot.expandStatusBar();
+
sUiBot.selectDataset("The Dude");
mLoginActivity.assertAutoFilled();
+ InstrumentedAutoFillService.assertNumberFillRequests(1);
}
/*
@@ -60,9 +74,16 @@
*
* - no dataset
* - multiple datasets
+ * - partitioned datasets (i.e., multiple fields)
* - response-level authentication (custom and fingerprint)
* - dataset-level authentication (custom and fingerprint)
*
+ * Save:
+ * - when no dataset is returned initially
+ * - when a dataset is returned initially
+ * - make sure password is set
+ * - test cases where non-savable-ids only are changed
+ *
* Other assertions:
* - illegal state thrown on callback calls
* - system server state after calls (for example, no pending callback)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 9900e19..89e5e02 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -36,10 +36,14 @@
private final UiDevice mDevice;
private final int mTimeout;
+ private final String mPackageName;
- UiBot(Instrumentation instrumentation, int timeout) {
+ UiBot(Instrumentation instrumentation, int timeout) throws Exception {
mDevice = UiDevice.getInstance(instrumentation);
mTimeout = timeout;
+ mPackageName = instrumentation.getContext().getPackageName();
+
+ collapseStatusBar();
}
/**
@@ -53,13 +57,29 @@
}
/**
- * Triggers the auto-fill affordance UI.
+ * Triggers IME by tapping a given field.
+ *
+ * @param id resource id, without the {@code +id} prefix
*/
- void triggerFillRequest() {
- Log.v(TAG, "triggerFillRequest()");
+ void triggerImeByRelativeId(String id) throws UiObjectNotFoundException {
+ Log.v(TAG, "triggerImeByRelativeId(): " + id);
+ final String fullId = mPackageName + ":id/" + id;
+ final UiObject field = mDevice.findObject(new UiSelector().resourceId(fullId));
+ final boolean clicked = field.clickAndWaitForNewWindow();
+ assertWithMessage("Failed to tap object with id '%s'", fullId).that(clicked).isTrue();
+ }
- // TODO(b/33197203): use id string when using real auto-fill bar
- clickOnNotification("AutoFill IME Emulation");
+ /**
+ * Taps a given UI object.
+ *
+ * @param id resource id, without the {@code +id} prefix
+ */
+ void tapByRelativeId(String id) throws UiObjectNotFoundException {
+ Log.v(TAG, "tapFieldByRelativeId(): " + id);
+ final String fullId = mPackageName + ":id/" + id;
+ final UiObject field = mDevice.findObject(new UiSelector().resourceId(fullId));
+ final boolean clicked = field.click();
+ assertWithMessage("Failed to tap object with id '%s'", fullId).that(clicked).isTrue();
}
/////////////////////////////////////////////////////////////////////////////////
@@ -127,6 +147,15 @@
assertWithMessage("could not find object with '%s'", text).that(uiObject.exists()).isTrue();
return uiObject;
}
+
+ void collapseStatusBar() throws Exception {
+ Helper.runShellCommand("service call statusbar 2");
+ }
+
+ void expandStatusBar() throws Exception {
+ Helper.runShellCommand("service call statusbar 1");
+ }
+
/////////////////////////////////////////
// End of temporary notification code. //
/////////////////////////////////////////
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 6861989..965dc1c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -1484,6 +1484,10 @@
checkMeteringRect(afRegions);
}
}
+ // ZSL must default to OFF
+ if (request.get(CONTROL_ENABLE_ZSL) != null) {
+ mCollector.expectKeyValueEquals(request, CONTROL_ENABLE_ZSL, false);
+ }
}
// Sensor settings.
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
index 802c500..b29fd57 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -531,6 +531,10 @@
waiverKeys.add(CaptureResult.SENSOR_DYNAMIC_WHITE_LEVEL);
}
+ if (!mStaticInfo.isEnableZslSupported()) {
+ waiverKeys.add(CaptureResult.CONTROL_ENABLE_ZSL);
+ }
+
if (mStaticInfo.isHardwareLevelAtLeastFull()) {
return waiverKeys;
}
@@ -765,6 +769,7 @@
resultKeys.add(CaptureResult.CONTROL_AF_STATE);
resultKeys.add(CaptureResult.CONTROL_AWB_STATE);
resultKeys.add(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST);
+ resultKeys.add(CaptureResult.CONTROL_ENABLE_ZSL);
resultKeys.add(CaptureResult.EDGE_MODE);
resultKeys.add(CaptureResult.FLASH_MODE);
resultKeys.add(CaptureResult.FLASH_STATE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index 15ff414..2357f54 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -24,6 +24,7 @@
import android.hardware.camera2.cts.CameraTestUtils.ImageVerifierListener;
import android.hardware.camera2.cts.testcases.Camera2MultiViewTestCase;
import android.hardware.camera2.cts.testcases.Camera2MultiViewTestCase.CameraPreviewListener;
+import android.hardware.camera2.params.OutputConfiguration;
import android.media.ImageReader;
import android.os.SystemClock;
import android.util.Log;
@@ -232,6 +233,36 @@
}
}
+ /*
+ * Verify behavior of sharing surfaces within one OutputConfiguration
+ */
+ public void testSharedSurfaces() throws Exception {
+ for (String cameraId : mCameraIds) {
+ try {
+ openCamera(cameraId);
+ if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+ Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+ continue;
+ }
+ if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + cameraId +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ testSharedSurfacesConfigByCamera(cameraId);
+
+ testSharedSurfacesCaptureSessionByCamera(cameraId);
+
+ testSharedDeferredSurfacesByCamera(cameraId);
+ }
+ finally {
+ closeCamera(cameraId);
+ }
+ }
+ }
+
+
/**
* Start camera preview using input texture views and/or one image reader
*/
@@ -287,4 +318,247 @@
stopPreview(cameraId);
}
+
+ /*
+ * Verify behavior of OutputConfiguration when sharing surfaces
+ */
+ private void testSharedSurfacesConfigByCamera(String cameraId) throws Exception {
+ Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+
+ SurfaceTexture[] previewTexture = new SurfaceTexture[2];
+ Surface[] surfaces = new Surface[2];
+
+ // Create surface textures with the same size
+ for (int i = 0; i < 2; i++) {
+ previewTexture[i] = getAvailableSurfaceTexture(
+ WAIT_FOR_COMMAND_TO_COMPLETE, mTextureView[i]);
+ assertNotNull("Unable to get preview surface texture", previewTexture[i]);
+ previewTexture[i].setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ // Correct the preview display rotation.
+ updatePreviewDisplayRotation(previewSize, mTextureView[i]);
+ surfaces[i] = new Surface(previewTexture[i]);
+ }
+
+ // Verify that outputConfiguration can be created with 2 surfaces with the same setting.
+ OutputConfiguration previewConfiguration = new OutputConfiguration(
+ OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
+ previewConfiguration.enableSurfaceSharing();
+ previewConfiguration.addSurface(surfaces[1]);
+ List<Surface> previewSurfaces = previewConfiguration.getSurfaces();
+ List<Surface> inputSurfaces = Arrays.asList(surfaces);
+ assertTrue(
+ String.format("Surfaces returned from getSurfaces() don't match those passed in"),
+ previewSurfaces.equals(inputSurfaces));
+
+ // Verify that outputConfiguration throws exception if 2 surfaces are different size
+ SurfaceTexture outputTexture2 = new SurfaceTexture(/* random texture ID*/ 5);
+ outputTexture2.setDefaultBufferSize(previewSize.getWidth()/2,
+ previewSize.getHeight()/2);
+ Surface outputSurface2 = new Surface(outputTexture2);
+ try {
+ OutputConfiguration configuration = new OutputConfiguration(
+ OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
+ configuration.enableSurfaceSharing();
+ configuration.addSurface(outputSurface2);
+ fail("No error for invalid output config created from different sizes of surfaces");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ // Verify that outputConfiguration throws exception if 2 surfaces are different format
+ ImageReader imageReader = makeImageReader(previewSize, ImageFormat.YUV_420_888,
+ MAX_READER_IMAGES, new ImageDropperListener(), mHandler);
+ try {
+ OutputConfiguration configuration = new OutputConfiguration(
+ OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
+ configuration.enableSurfaceSharing();
+ configuration.addSurface(imageReader.getSurface());
+ fail("No error for invalid output config created from different format surfaces");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ // Verify that outputConfiguration can be created with deferred surface with the same
+ // setting.
+ OutputConfiguration deferredPreviewConfigure = new OutputConfiguration(
+ previewSize, SurfaceTexture.class);
+ deferredPreviewConfigure.addSurface(surfaces[0]);
+ assertTrue(String.format("Number of surfaces %d doesn't match expected value 1",
+ deferredPreviewConfigure.getSurfaces().size()),
+ deferredPreviewConfigure.getSurfaces().size() == 1);
+ assertEquals("Surface 0 in OutputConfiguration doesn't match input",
+ deferredPreviewConfigure.getSurfaces().get(0), surfaces[0]);
+
+ // Verify that outputConfiguration throws exception if deferred surface and non-deferred
+ // surface properties don't match
+ try {
+ OutputConfiguration configuration = new OutputConfiguration(
+ previewSize, SurfaceTexture.class);
+ configuration.addSurface(imageReader.getSurface());
+ fail("No error for invalid output config created deferred class with different type");
+ } catch (IllegalArgumentException e) {
+ // expected;
+ }
+ }
+
+ private void testSharedSurfacesCaptureSessionByCamera(String cameraId) throws Exception {
+ Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+ CameraPreviewListener[] previewListener = new CameraPreviewListener[2];
+ SurfaceTexture[] previewTexture = new SurfaceTexture[2];
+ Surface[] surfaces = new Surface[2];
+
+ // Create surface textures with the same size
+ for (int i = 0; i < 2; i++) {
+ previewListener[i] = new CameraPreviewListener();
+ mTextureView[i].setSurfaceTextureListener(previewListener[i]);
+ previewTexture[i] = getAvailableSurfaceTexture(
+ WAIT_FOR_COMMAND_TO_COMPLETE, mTextureView[i]);
+ assertNotNull("Unable to get preview surface texture", previewTexture[i]);
+ previewTexture[i].setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ // Correct the preview display rotation.
+ updatePreviewDisplayRotation(previewSize, mTextureView[i]);
+ surfaces[i] = new Surface(previewTexture[i]);
+ }
+
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+
+ // Create shared outputs for the two surface textures
+ OutputConfiguration surfaceSharedOutput = new OutputConfiguration(
+ OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
+ surfaceSharedOutput.enableSurfaceSharing();
+ surfaceSharedOutput.addSurface(surfaces[1]);
+
+ List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+ outputConfigurations.add(surfaceSharedOutput);
+
+ startPreviewWithConfigs(cameraId, outputConfigurations, null);
+
+ for (int i = 0; i < 2; i++) {
+ boolean previewDone =
+ previewListener[i].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview " + i, previewDone);
+ mTextureView[i].setSurfaceTextureListener(null);
+ }
+
+ SystemClock.sleep(PREVIEW_TIME_MS);
+
+ stopPreview(cameraId);
+ }
+
+ private void testSharedDeferredSurfacesByCamera(String cameraId) throws Exception {
+ Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+ CameraPreviewListener[] previewListener = new CameraPreviewListener[2];
+ SurfaceTexture[] previewTexture = new SurfaceTexture[2];
+ Surface[] surfaces = new Surface[2];
+
+ // Create surface textures with the same size
+ for (int i = 0; i < 2; i++) {
+ previewListener[i] = new CameraPreviewListener();
+ mTextureView[i].setSurfaceTextureListener(previewListener[i]);
+ previewTexture[i] = getAvailableSurfaceTexture(
+ WAIT_FOR_COMMAND_TO_COMPLETE, mTextureView[i]);
+ assertNotNull("Unable to get preview surface texture", previewTexture[i]);
+ previewTexture[i].setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ // Correct the preview display rotation.
+ updatePreviewDisplayRotation(previewSize, mTextureView[i]);
+ surfaces[i] = new Surface(previewTexture[i]);
+ }
+
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+
+ //
+ // Create deferred outputConfiguration, addSurface, createCaptureSession, addSurface, and
+ // finalizeOutputConfigurations.
+ //
+
+ OutputConfiguration surfaceSharedOutput = new OutputConfiguration(
+ previewSize, SurfaceTexture.class);
+ surfaceSharedOutput.enableSurfaceSharing();
+ surfaceSharedOutput.addSurface(surfaces[0]);
+
+ List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+ outputConfigurations.add(surfaceSharedOutput);
+
+ // Run preview with one surface, and verify at least one frame is received.
+ startPreviewWithConfigs(cameraId, outputConfigurations, null);
+ boolean previewDone =
+ previewListener[0].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview 0", previewDone);
+
+ SystemClock.sleep(PREVIEW_TIME_MS);
+
+ // Add deferred surface to the output configuration
+ surfaceSharedOutput.addSurface(surfaces[1]);
+ List<OutputConfiguration> deferredConfigs = new ArrayList<OutputConfiguration>();
+ deferredConfigs.add(surfaceSharedOutput);
+
+ // Run preview with both surfaces, and verify at least one frame is received for each
+ // surface.
+ updateOutputConfigs(cameraId, deferredConfigs, null);
+ previewDone =
+ previewListener[1].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview 1", previewDone);
+
+ stopPreview(cameraId);
+
+ previewListener[0].reset();
+ previewListener[1].reset();
+
+ //
+ // Create outputConfiguration with a surface, createCaptureSession, addSurface, and
+ // finalizeOutputConfigurations.
+ //
+
+ surfaceSharedOutput = new OutputConfiguration(
+ OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
+ surfaceSharedOutput.enableSurfaceSharing();
+ outputConfigurations.clear();
+ outputConfigurations.add(surfaceSharedOutput);
+
+ startPreviewWithConfigs(cameraId, outputConfigurations, null);
+ previewDone =
+ previewListener[0].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview 0", previewDone);
+
+ // Add deferred surface to the output configuration, and continue running preview
+ surfaceSharedOutput.addSurface(surfaces[1]);
+ deferredConfigs.clear();
+ deferredConfigs.add(surfaceSharedOutput);
+ updateOutputConfigs(cameraId, deferredConfigs, null);
+ previewDone =
+ previewListener[1].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview 1", previewDone);
+
+ SystemClock.sleep(PREVIEW_TIME_MS);
+ stopPreview(cameraId);
+
+ previewListener[0].reset();
+ previewListener[1].reset();
+
+ //
+ // Create deferred output configuration, createCaptureSession, addSurface, addSurface, and
+ // finalizeOutputConfigurations.
+
+ surfaceSharedOutput = new OutputConfiguration(
+ previewSize, SurfaceTexture.class);
+ surfaceSharedOutput.enableSurfaceSharing();
+ outputConfigurations.clear();
+ outputConfigurations.add(surfaceSharedOutput);
+ createSessionWithConfigs(cameraId, outputConfigurations);
+
+ // Add 2 surfaces to the output configuration, and run preview
+ surfaceSharedOutput.addSurface(surfaces[0]);
+ surfaceSharedOutput.addSurface(surfaces[1]);
+ deferredConfigs.clear();
+ deferredConfigs.add(surfaceSharedOutput);
+ updateOutputConfigs(cameraId, deferredConfigs, null);
+ for (int i = 0; i < 2; i++) {
+ previewDone =
+ previewListener[i].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview " + i, previewDone);
+ }
+
+ SystemClock.sleep(PREVIEW_TIME_MS);
+ stopPreview(cameraId);
+ }
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
index a9b4ad2..6c48418 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -103,7 +103,7 @@
/**
* Test normal still capture sequence.
* <p>
- * Preview and and jpeg output streams are configured. Max still capture
+ * Preview and jpeg output streams are configured. Max still capture
* size is used for jpeg capture. The sequence of still capture being test
* is: start preview, auto focus, precapture metering (if AE is not
* converged), then capture jpeg. The AWB and AE are in auto modes. AF mode
@@ -128,6 +128,38 @@
}
/**
+ * Test ZSL still capture sequence.
+ * <p>
+ * Preview and jpeg output streams are configured. Max still capture
+ * size is used for jpeg capture. The sequence of still capture being test
+ * is: start preview, auto focus, precapture metering (if AE is not
+ * converged), then capture jpeg. The AWB and AE are in auto modes. AF mode
+ * is CONTINUOUS_PICTURE. Same as testTakePicture, but with enableZSL set.
+ * </p>
+ */
+ public void testTakePictureZsl() throws Exception{
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing basic ZSL take picture for Camera " + id);
+ openDevice(id);
+ if (!mStaticInfo.isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+ continue;
+ }
+ CaptureRequest.Builder stillRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
+ takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null, /*afRegions*/null,
+ /*addAeTriggerCancel*/false, /*allocateBitmap*/false,
+ /*previewRequest*/null, stillRequest);
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
* Test basic Raw capture. Raw buffer avaiablility is checked, but raw buffer data is not.
*/
public void testBasicRawCapture() throws Exception {
@@ -143,7 +175,33 @@
continue;
}
- rawCaptureTestByCamera();
+ rawCaptureTestByCamera(/*stillRequest*/null);
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test basic Raw ZSL capture. Raw buffer avaiablility is checked, but raw buffer data is not.
+ */
+ public void testBasicRawZslCapture() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing raw ZSL capture for Camera " + mCameraIds[i]);
+ openDevice(mCameraIds[i]);
+
+ if (!mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+ Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+ ". Skip the test.");
+ continue;
+ }
+ CaptureRequest.Builder stillRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
+ rawCaptureTestByCamera(stillRequest);
} finally {
closeDevice();
closeImageReader();
@@ -163,7 +221,7 @@
public void testFullRawCapture() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
try {
- Log.i(TAG, "Testing raw capture for Camera " + mCameraIds[i]);
+ Log.i(TAG, "Testing raw+JPEG capture for Camera " + mCameraIds[i]);
openDevice(mCameraIds[i]);
if (!mStaticInfo.isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
@@ -172,13 +230,44 @@
continue;
}
- fullRawCaptureTestByCamera();
+ fullRawCaptureTestByCamera(/*stillRequest*/null);
} finally {
closeDevice();
closeImageReader();
}
}
}
+
+ /**
+ * Test the full raw capture ZSL use case.
+ *
+ * This includes:
+ * - Configuring the camera with a preview, jpeg, and raw output stream.
+ * - Running preview until AE/AF can settle.
+ * - Capturing with a request targeting all three output streams.
+ */
+ public void testFullRawZSLCapture() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing raw+JPEG ZSL capture for Camera " + mCameraIds[i]);
+ openDevice(mCameraIds[i]);
+ if (!mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+ Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+ ". Skip the test.");
+ continue;
+ }
+ CaptureRequest.Builder stillRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
+ fullRawCaptureTestByCamera(stillRequest);
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
/**
* Test touch for focus.
* <p>
@@ -376,7 +465,8 @@
continue;
}
takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null, /*afRegions*/null,
- /*addAeTriggerCancel*/true, /*allocateBitmap*/false);
+ /*addAeTriggerCancel*/true, /*allocateBitmap*/false,
+ /*previewRequest*/null, /*stillRequest*/null);
} finally {
closeDevice();
closeImageReader();
@@ -402,7 +492,8 @@
continue;
}
takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null, /*afRegions*/null,
- /*addAeTriggerCancel*/false, /*allocateBitmap*/true);
+ /*addAeTriggerCancel*/false, /*allocateBitmap*/true,
+ /*previewRequest*/null, /*stillRequest*/null);
} finally {
closeDevice();
closeImageReader();
@@ -469,7 +560,8 @@
MeteringRectangle[] aeRegions, MeteringRectangle[] awbRegions,
MeteringRectangle[] afRegions) throws Exception {
takePictureTestByCamera(aeRegions, awbRegions, afRegions,
- /*addAeTriggerCancel*/false, /*allocateBitmap*/false);
+ /*addAeTriggerCancel*/false, /*allocateBitmap*/false,
+ /*previewRequest*/null, /*stillRequest*/null);
}
/**
@@ -488,10 +580,13 @@
* @param afRegions AF regions for this capture
* @param addAeTriggerCancel If a AE precapture trigger cancel is sent after the trigger.
* @param allocateBitmap If a set of bitmaps are allocated during the test for memory test.
+ * @param previewRequest The preview request builder to use, or null to use the default
+ * @param stillRequest The still capture request to use, or null to use the default
*/
private void takePictureTestByCamera(
MeteringRectangle[] aeRegions, MeteringRectangle[] awbRegions,
- MeteringRectangle[] afRegions, boolean addAeTriggerCancel, boolean allocateBitmap)
+ MeteringRectangle[] afRegions, boolean addAeTriggerCancel, boolean allocateBitmap,
+ CaptureRequest.Builder previewRequest, CaptureRequest.Builder stillRequest)
throws Exception {
boolean hasFocuser = mStaticInfo.hasFocuser();
@@ -501,10 +596,12 @@
CaptureResult result;
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
- CaptureRequest.Builder previewRequest =
- mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- CaptureRequest.Builder stillRequest =
- mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ if (previewRequest == null) {
+ previewRequest = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ }
+ if (stillRequest == null) {
+ stillRequest = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ }
prepareStillCaptureAndStartPreview(previewRequest, stillRequest, maxPreviewSz,
maxStillSz, resultListener, imageListener);
@@ -734,14 +831,14 @@
/**
* Basic raw capture test for each camera.
*/
- private void rawCaptureTestByCamera() throws Exception {
+ private void rawCaptureTestByCamera(CaptureRequest.Builder stillRequest) throws Exception {
Size maxPreviewSz = mOrderedPreviewSizes.get(0);
Size size = mStaticInfo.getRawDimensChecked();
// Prepare raw capture and start preview.
CaptureRequest.Builder previewBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- CaptureRequest.Builder rawBuilder =
+ CaptureRequest.Builder rawBuilder = (stillRequest != null) ? stillRequest :
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
@@ -772,7 +869,7 @@
stopPreview();
}
- private void fullRawCaptureTestByCamera() throws Exception {
+ private void fullRawCaptureTestByCamera(CaptureRequest.Builder stillRequest) throws Exception {
Size maxPreviewSz = mOrderedPreviewSizes.get(0);
Size maxStillSz = mOrderedStillSizes.get(0);
@@ -790,7 +887,7 @@
// Prepare raw capture and start preview.
CaptureRequest.Builder previewBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- CaptureRequest.Builder multiBuilder =
+ CaptureRequest.Builder multiBuilder = (stillRequest != null) ? stillRequest :
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
ImageReader rawReader = null;
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index 6663201..2538645 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -538,18 +538,18 @@
}
// Add deferred surfaces to their configurations
- surfaceViewOutput.setDeferredSurface(mPreviewSurface);
- surfaceTextureOutput.setDeferredSurface(sharedOutputSurface1);
+ surfaceViewOutput.addSurface(mPreviewSurface);
+ surfaceTextureOutput.addSurface(sharedOutputSurface1);
- // Verify bad inputs to setDeferredSurface
+ // Verify bad inputs to addSurface
try {
- surfaceViewOutput.setDeferredSurface(null);
+ surfaceViewOutput.addSurface(null);
fail("No error from setting a null deferred surface");
} catch (NullPointerException e) {
// expected
}
try {
- surfaceViewOutput.setDeferredSurface(mPreviewSurface);
+ surfaceViewOutput.addSurface(mPreviewSurface);
fail("Shouldn't be able to set deferred surface twice");
} catch (IllegalStateException e) {
// expected
@@ -559,12 +559,12 @@
List<OutputConfiguration> deferredSurfaces = new ArrayList<>();
deferredSurfaces.add(surfaceTextureOutput);
- mSession.finishDeferredConfiguration(deferredSurfaces);
+ mSession.finalizeOutputConfigurations(deferredSurfaces);
// Try a second time, this should error
try {
- mSession.finishDeferredConfiguration(deferredSurfaces);
+ mSession.finalizeOutputConfigurations(deferredSurfaces);
fail("Should have received ISE for trying to finish a deferred output twice");
} catch (IllegalArgumentException e) {
// expected
@@ -593,7 +593,7 @@
deferredSurfaces.clear();
deferredSurfaces.add(surfaceViewOutput);
- mSession.finishDeferredConfiguration(deferredSurfaces);
+ mSession.finalizeOutputConfigurations(deferredSurfaces);
// Use final deferred surface for a bit
imageListener.resetImageCount();
@@ -611,16 +611,16 @@
new OutputConfiguration(maxPreviewSize, SurfaceTexture.class);
deferredSurfaces.clear();
try {
- mSession.finishDeferredConfiguration(deferredSurfaces);
- fail("No error for empty list passed to finishDeferredConfiguration");
+ mSession.finalizeOutputConfigurations(deferredSurfaces);
+ fail("No error for empty list passed to finalizeOutputConfigurations");
} catch (IllegalArgumentException e) {
// expected
}
deferredSurfaces.add(badConfig);
try {
- mSession.finishDeferredConfiguration(deferredSurfaces);
- fail("No error for invalid output config being passed to finishDeferredConfiguration");
+ mSession.finalizeOutputConfigurations(deferredSurfaces);
+ fail("No error for invalid output config being passed to finalizeOutputConfigurations");
} catch (IllegalArgumentException e) {
// expected
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index 07e810f..dea3cfd 100644
--- a/tests/camera/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -2224,6 +2224,13 @@
}
/**
+ * Check if the enable ZSL key is supported.
+ */
+ public boolean isEnableZslSupported() {
+ return areKeysAvailable(CaptureRequest.CONTROL_ENABLE_ZSL);
+ }
+
+ /**
* Get the value in index for a fixed-size array from a given key.
*
* <p>If the camera device is incorrectly reporting values, log a warning and return
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index 39bf0a5..a8180d1 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -33,6 +33,7 @@
import android.hardware.camera2.cts.Camera2MultiViewCtsActivity;
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
+import android.hardware.camera2.params.OutputConfiguration;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -116,7 +117,7 @@
mHandler = null;
mCameraListener = null;
for (CameraHolder camera : mCameraHolders) {
- if (camera.isOpenned()) {
+ if (camera.isOpened()) {
camera.close();
camera = null;
}
@@ -220,7 +221,7 @@
protected void openCamera(String cameraId) throws Exception {
CameraHolder camera = getCameraHolder(cameraId);
- assertFalse("Camera has already opened", camera.isOpenned());
+ assertFalse("Camera has already opened", camera.isOpened());
camera.open();
return;
}
@@ -230,29 +231,51 @@
camera.close();
}
+ protected void createSessionWithConfigs(String cameraId, List<OutputConfiguration> configs)
+ throws Exception {
+ CameraHolder camera = getCameraHolder(cameraId);
+ camera.createSessionWithConfigs(configs);
+ }
+
protected void startPreview(
String cameraId, List<Surface> outputSurfaces, CaptureCallback listener)
throws Exception {
CameraHolder camera = getCameraHolder(cameraId);
- assertTrue("Camera " + cameraId + " is not openned", camera.isOpenned());
+ assertTrue("Camera " + cameraId + " is not openned", camera.isOpened());
camera.startPreview(outputSurfaces, listener);
}
+ protected void startPreviewWithConfigs(String cameraId,
+ List<OutputConfiguration> outputConfigs,
+ CaptureCallback listener)
+ throws Exception {
+ CameraHolder camera = getCameraHolder(cameraId);
+ assertTrue("Camera " + cameraId + " is not openned", camera.isOpened());
+ camera.startPreviewWithConfigs(outputConfigs, listener);
+ }
+
protected void stopPreview(String cameraId) throws Exception {
CameraHolder camera = getCameraHolder(cameraId);
assertTrue("Camera " + cameraId + " preview is not running", camera.isPreviewStarted());
camera.stopPreview();
}
+ protected void updateOutputConfigs(String cameraId, List<OutputConfiguration> configs,
+ CaptureCallback listener) throws Exception {
+ CameraHolder camera = getCameraHolder(cameraId);
+ assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+ camera.updateOutputConfigs(configs, listener);
+ }
+
protected StaticMetadata getStaticInfo(String cameraId) {
CameraHolder camera = getCameraHolder(cameraId);
- assertTrue("Camera is not openned", camera.isOpenned());
+ assertTrue("Camera is not openned", camera.isOpened());
return camera.getStaticInfo();
}
protected List<Size> getOrderedPreviewSizes(String cameraId) {
CameraHolder camera = getCameraHolder(cameraId);
- assertTrue("Camera is not openned", camera.isOpenned());
+ assertTrue("Camera is not openned", camera.isOpened());
return camera.getOrderedPreviewSizes();
}
@@ -325,6 +348,12 @@
mPreviewDone.close();
return true;
}
+
+ /** Reset the Listener */
+ public void reset() {
+ mFirstPreviewAvailable = false;
+ mPreviewDone.close();
+ }
}
private CameraHolder getCameraHolder(String cameraId) {
@@ -370,12 +399,12 @@
assertNotNull(String.format("Failed to open camera device ID: %s", mCameraId), mCamera);
}
- public boolean isOpenned() {
+ public boolean isOpened() {
return (mCamera != null);
}
public void close() throws Exception {
- if (!isOpenned()) {
+ if (!isOpened()) {
return;
}
mCamera.close();
@@ -401,6 +430,43 @@
mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
}
+ public void createSessionWithConfigs(List<OutputConfiguration> outputConfigs)
+ throws Exception {
+ mSessionListener = new BlockingSessionCallback();
+ mSession = configureCameraSessionWithConfig(mCamera, outputConfigs, mSessionListener, mHandler);
+ }
+
+ public void startPreviewWithConfigs(List<OutputConfiguration> outputConfigs,
+ CaptureCallback listener)
+ throws Exception {
+ createSessionWithConfigs(outputConfigs);
+
+ CaptureRequest.Builder captureBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ for (OutputConfiguration config : outputConfigs) {
+ for (Surface surface : config.getSurfaces()) {
+ captureBuilder.addTarget(surface);
+ }
+ }
+ mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+ }
+
+ public void updateOutputConfigs(List<OutputConfiguration> configs,
+ CaptureCallback listener) throws Exception {
+ mSession.finalizeOutputConfigurations(configs);
+
+ CaptureRequest.Builder captureBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ for (OutputConfiguration config : configs) {
+ for (Surface surface : config.getSurfaces()) {
+ captureBuilder.addTarget(surface);
+ }
+ }
+ mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+ }
+
public boolean isPreviewStarted() {
return (mSession != null);
}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java b/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
index 46d0898..42b6684 100644
--- a/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
@@ -27,8 +27,10 @@
import com.android.cts.core.runner.support.ExtendedAndroidRunnerBuilder;
import com.google.common.base.Splitter;
import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
import java.io.FileReader;
import java.io.IOException;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -39,6 +41,7 @@
import org.junit.runner.Computer;
import org.junit.runner.JUnitCore;
import org.junit.runner.Request;
+import org.junit.runner.Result;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
@@ -289,16 +292,27 @@
Log.d(TAG, "Finished preparations, running/listing tests");
Bundle results = new Bundle();
+ Result junitResults = new Result();
try {
- core.run(Request.runner(runner));
+ junitResults = core.run(Request.runner(runner));
} catch (RuntimeException e) {
final String msg = "Fatal exception when running tests";
Log.e(TAG, msg, e);
// report the exception to instrumentation out
results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
msg + "\n" + Log.getStackTraceString(e));
+ } finally {
+ ByteArrayOutputStream summaryStream = new ByteArrayOutputStream();
+ // create the stream used to output summary data to the user
+ PrintStream summaryWriter = new PrintStream(summaryStream);
+ instrumentationResultPrinter.instrumentationRunFinished(summaryWriter,
+ results, junitResults);
+ summaryWriter.close();
+ results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+ String.format("\n%s", summaryStream.toString()));
}
+
Log.d(TAG, "Finished");
finish(Activity.RESULT_OK, results);
}
diff --git a/tests/inputmethod/Android.mk b/tests/inputmethod/Android.mk
new file mode 100644
index 0000000..7de22d4
--- /dev/null
+++ b/tests/inputmethod/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MULTILIB := both
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ compatibility-device-util \
+ ctstestrunner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsInputMethodTestCases
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
new file mode 100644
index 0000000..11f008d
--- /dev/null
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.view.inputmethod.cts">
+
+ <application
+ android:label="CtsInputMethodTestCases"
+ android:multiArch="true"
+ android:supportsRtl="true">
+
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="android.view.inputmethod.cts.InputMethodCtsActivity"
+ android:label="InputMethodCtsActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests of android.view.inputmethod"
+ android:targetPackage="android.view.inputmethod.cts">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
new file mode 100644
index 0000000..ed1e6be
--- /dev/null
+++ b/tests/inputmethod/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<configuration description="Config for CTS InputMethod test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsInputMethodTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.view.inputmethod.cts" />
+ <option name="runtime-hint" value="1m0s" />
+ </test>
+</configuration>
diff --git a/tests/inputmethod/res/layout/inputmethod_edittext.xml b/tests/inputmethod/res/layout/inputmethod_edittext.xml
new file mode 100644
index 0000000..a8f442e
--- /dev/null
+++ b/tests/inputmethod/res/layout/inputmethod_edittext.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/blue"
+ android:padding="10px">
+
+ <EditText
+ android:id="@+id/entry"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:drawable/editbox_background"/>
+
+</RelativeLayout>
diff --git a/tests/inputmethod/res/values/colors.xml b/tests/inputmethod/res/values/colors.xml
new file mode 100644
index 0000000..1d87ea8
--- /dev/null
+++ b/tests/inputmethod/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <drawable name="blue">#770000ff</drawable>
+</resources>
diff --git a/tests/inputmethod/res/xml/keyboard.xml b/tests/inputmethod/res/xml/keyboard.xml
new file mode 100644
index 0000000..af8b23b
--- /dev/null
+++ b/tests/inputmethod/res/xml/keyboard.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="10px"
+ >
+
+ <Row>
+ <Key android:codes="-1" android:keyLabel="Sticky!"
+ android:isModifier="true" android:isSticky="true" />
+ <Key android:codes="120" android:keyLabel="x" />
+ </Row>
+</Keyboard>
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
new file mode 100644
index 0000000..8089739
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.R;
+import android.view.inputmethod.cts.util.InputConnectionTestUtils;
+import android.widget.EditText;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BaseInputConnectionTest {
+ private Instrumentation mInstrumentation;
+ private InputMethodCtsActivity mActivity;
+ private Window mWindow;
+ private EditText mView;
+ private BaseInputConnection mConnection;
+
+ @Rule
+ public ActivityTestRule<InputMethodCtsActivity> mActivityRule =
+ new ActivityTestRule<>(InputMethodCtsActivity.class);
+
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+ PollingCheck.waitFor(mActivity::hasWindowFocus);
+ mWindow = mActivity.getWindow();
+ mView = (EditText) mWindow.findViewById(R.id.entry);
+ mConnection = new BaseInputConnection(mView, true);
+ }
+
+ @Test
+ public void testDefaultMethods() {
+ // These methods are default to return fixed result.
+
+ assertFalse(mConnection.beginBatchEdit());
+ assertFalse(mConnection.endBatchEdit());
+
+ // only fit for test default implementation of commitCompletion.
+ int completionId = 1;
+ String completionString = "commitCompletion test";
+ assertFalse(mConnection.commitCompletion(new CompletionInfo(completionId,
+ 0, completionString)));
+
+ assertNull(mConnection.getExtractedText(new ExtractedTextRequest(), 0));
+
+ // only fit for test default implementation of performEditorAction.
+ int actionCode = 1;
+ int actionId = 2;
+ String action = "android.intent.action.MAIN";
+ assertTrue(mConnection.performEditorAction(actionCode));
+ assertFalse(mConnection.performContextMenuAction(actionId));
+ assertFalse(mConnection.performPrivateCommand(action, new Bundle()));
+ }
+
+ @Test
+ public void testOpComposingSpans() {
+ Spannable text = new SpannableString("Test ComposingSpans");
+ BaseInputConnection.setComposingSpans(text);
+ assertTrue(BaseInputConnection.getComposingSpanStart(text) > -1);
+ assertTrue(BaseInputConnection.getComposingSpanEnd(text) > -1);
+ BaseInputConnection.removeComposingSpans(text);
+ assertTrue(BaseInputConnection.getComposingSpanStart(text) == -1);
+ assertTrue(BaseInputConnection.getComposingSpanEnd(text) == -1);
+ }
+
+ /**
+ * getEditable: Return the target of edit operations. The default implementation
+ * returns its own fake editable that is just used for composing text.
+ * clearMetaKeyStates: Default implementation uses
+ * MetaKeyKeyListener#clearMetaKeyState(long, int) to clear the state.
+ * BugId:1738511
+ * commitText: 1. Default implementation replaces any existing composing text with the given
+ * text.
+ * 2. In addition, only if dummy mode, a key event is sent for the new text and the
+ * current editable buffer cleared.
+ * deleteSurroundingText: The default implementation performs the deletion around the current
+ * selection position of the editable text.
+ * getCursorCapsMode: 1. The default implementation uses TextUtils.getCapsMode to get the
+ * cursor caps mode for the current selection position in the editable text.
+ * TextUtils.getCapsMode is tested fully in TextUtilsTest#testGetCapsMode.
+ * 2. In dummy mode in which case 0 is always returned.
+ * getTextBeforeCursor, getTextAfterCursor: The default implementation performs the deletion
+ * around the current selection position of the editable text.
+ * setSelection: changes the selection position in the current editable text.
+ */
+ @Test
+ public void testOpTextMethods() throws Throwable {
+ // return is an default Editable instance with empty source
+ final Editable text = mConnection.getEditable();
+ assertNotNull(text);
+ assertEquals(0, text.length());
+
+ // Test commitText, not dummy mode
+ CharSequence str = "TestCommit ";
+ Editable inputText = Editable.Factory.getInstance().newEditable(str);
+ mConnection.commitText(inputText, inputText.length());
+ final Editable text2 = mConnection.getEditable();
+ int strLength = str.length();
+ assertEquals(strLength, text2.length());
+ assertEquals(str.toString(), text2.toString());
+ assertEquals(TextUtils.CAP_MODE_WORDS,
+ mConnection.getCursorCapsMode(TextUtils.CAP_MODE_WORDS));
+ int offLength = 3;
+ CharSequence expected = str.subSequence(strLength - offLength, strLength);
+ assertEquals(expected.toString(), mConnection.getTextBeforeCursor(offLength,
+ BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
+ mConnection.setSelection(0, 0);
+ expected = str.subSequence(0, offLength);
+ assertEquals(expected.toString(), mConnection.getTextAfterCursor(offLength,
+ BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
+
+ mActivityRule.runOnUiThread(() -> {
+ assertTrue(mView.requestFocus());
+ assertTrue(mView.isFocused());
+ });
+
+ // dummy mode
+ BaseInputConnection dummyConnection = new BaseInputConnection(mView, false);
+ dummyConnection.commitText(inputText, inputText.length());
+ PollingCheck.waitFor(() -> text2.toString().equals(mView.getText().toString()));
+ assertEquals(0, dummyConnection.getCursorCapsMode(TextUtils.CAP_MODE_WORDS));
+
+ // Test deleteSurroundingText
+ int end = text2.length();
+ mConnection.setSelection(end, end);
+ // Delete the ending space
+ assertTrue(mConnection.deleteSurroundingText(1, 2));
+ Editable text3 = mConnection.getEditable();
+ assertEquals(strLength - 1, text3.length());
+ String expectedDelString = "TestCommit";
+ assertEquals(expectedDelString, text3.toString());
+ }
+
+ /**
+ * finishComposingText: 1. The default implementation removes the composing state from the
+ * current editable text.
+ * 2. In addition, only if dummy mode, a key event is sent for the new
+ * text and the current editable buffer cleared.
+ * setComposingText: The default implementation places the given text into the editable,
+ * replacing any existing composing text
+ */
+ @Test
+ public void testFinishComposingText() throws Throwable {
+ CharSequence str = "TestFinish";
+ Editable inputText = Editable.Factory.getInstance().newEditable(str);
+ mConnection.commitText(inputText, inputText.length());
+ final Editable text = mConnection.getEditable();
+ // Test finishComposingText, not dummy mode
+ BaseInputConnection.setComposingSpans(text);
+ assertTrue(BaseInputConnection.getComposingSpanStart(text) > -1);
+ assertTrue(BaseInputConnection.getComposingSpanEnd(text) > -1);
+ mConnection.finishComposingText();
+ assertTrue(BaseInputConnection.getComposingSpanStart(text) == -1);
+ assertTrue(BaseInputConnection.getComposingSpanEnd(text) == -1);
+
+ mActivityRule.runOnUiThread(() -> {
+ assertTrue(mView.requestFocus());
+ assertTrue(mView.isFocused());
+ });
+
+ // dummy mode
+ BaseInputConnection dummyConnection = new BaseInputConnection(mView, false);
+ dummyConnection.setComposingText(str, str.length());
+ dummyConnection.finishComposingText();
+ PollingCheck.waitFor(() -> text.toString().equals(mView.getText().toString()));
+ }
+
+ /**
+ * Provides standard implementation for sending a key event to the window
+ * attached to the input connection's view
+ */
+ @Test
+ public void testSendKeyEvent() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ assertTrue(mView.requestFocus());
+ assertTrue(mView.isFocused());
+ });
+
+ // 12-key support
+ KeyCharacterMap keymap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ if (keymap.getKeyboardType() == KeyCharacterMap.NUMERIC) {
+ // 'Q' in case of 12-key(NUMERIC) keyboard
+ mConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_7));
+ mConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_7));
+ } else {
+ mInstrumentation.sendStringSync("q");
+ mInstrumentation.waitForIdleSync();
+ }
+ PollingCheck.waitFor(() -> "q".equals(mView.getText().toString()));
+ }
+
+ /**
+ * Updates InputMethodManager with the current fullscreen mode.
+ */
+ @Test
+ public void testReportFullscreenMode() {
+ InputMethodManager imManager = (InputMethodManager) mInstrumentation.getTargetContext()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ mConnection.reportFullscreenMode(false);
+ assertFalse(imManager.isFullscreenMode());
+ mConnection.reportFullscreenMode(true);
+ // Only IMEs are allowed to report full-screen mode. Calling this method from the
+ // application should have no effect.
+ assertFalse(imManager.isFullscreenMode());
+ }
+
+ /**
+ * An utility method to create an instance of {@link BaseInputConnection} in dummy mode with
+ * an initial text and selection range.
+ * @param view the {@link View} to be associated with the {@link BaseInputConnection}.
+ * @param source the initial text.
+ * @return {@link BaseInputConnection} instantiated in dummy mode with {@code source} and
+ * selection range from {@code selectionStart} to {@code selectionEnd}
+ */
+ private static BaseInputConnection createDummyConnectionWithSelection(
+ final View view, final CharSequence source) {
+ final int selectionStart = Selection.getSelectionStart(source);
+ final int selectionEnd = Selection.getSelectionEnd(source);
+ final Editable editable = Editable.Factory.getInstance().newEditable(source);
+ Selection.setSelection(editable, selectionStart, selectionEnd);
+ return new BaseInputConnection(view, false) {
+ @Override
+ public Editable getEditable() {
+ return editable;
+ }
+ };
+ }
+
+ private void verifyDeleteSurroundingTextMain(final String initialState,
+ final int deleteBefore, final int deleteAfter, final String expectedState) {
+ final CharSequence source = InputConnectionTestUtils.formatString(initialState);
+ final BaseInputConnection ic = createDummyConnectionWithSelection(mView, source);
+ ic.deleteSurroundingText(deleteBefore, deleteAfter);
+
+ final CharSequence expectedString = InputConnectionTestUtils.formatString(expectedState);
+ final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
+ final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
+
+ // It is sufficient to check the surrounding text up to source.length() characters, because
+ // InputConnection.deleteSurroundingText() is not supposed to increase the text length.
+ final int retrievalLength = source.length();
+ if (expectedSelectionStart == 0) {
+ assertTrue(TextUtils.isEmpty(ic.getTextBeforeCursor(retrievalLength, 0)));
+ } else {
+ assertEquals(expectedString.subSequence(0, expectedSelectionStart).toString(),
+ ic.getTextBeforeCursor(retrievalLength, 0).toString());
+ }
+ if (expectedSelectionStart == expectedSelectionEnd) {
+ assertTrue(TextUtils.isEmpty(ic.getSelectedText(0))); // null is allowed.
+ } else {
+ assertEquals(expectedString.subSequence(expectedSelectionStart,
+ expectedSelectionEnd).toString(), ic.getSelectedText(0).toString());
+ }
+ if (expectedSelectionEnd == expectedString.length()) {
+ assertTrue(TextUtils.isEmpty(ic.getTextAfterCursor(retrievalLength, 0)));
+ } else {
+ assertEquals(expectedString.subSequence(expectedSelectionEnd,
+ expectedString.length()).toString(),
+ ic.getTextAfterCursor(retrievalLength, 0).toString());
+ }
+ }
+
+ /**
+ * Tests {@link BaseInputConnection#deleteSurroundingText(int, int)} comprehensively.
+ */
+ @Test
+ public void testDeleteSurroundingText() throws Throwable {
+ verifyDeleteSurroundingTextMain("012[]3456789", 0, 0, "012[]3456789");
+ verifyDeleteSurroundingTextMain("012[]3456789", -1, -1, "012[]3456789");
+ verifyDeleteSurroundingTextMain("012[]3456789", 1, 2, "01[]56789");
+ verifyDeleteSurroundingTextMain("012[]3456789", 10, 1, "[]456789");
+ verifyDeleteSurroundingTextMain("012[]3456789", 1, 10, "01[]");
+ verifyDeleteSurroundingTextMain("[]0123456789", 3, 3, "[]3456789");
+ verifyDeleteSurroundingTextMain("0123456789[]", 3, 3, "0123456[]");
+ verifyDeleteSurroundingTextMain("012[345]6789", 0, 0, "012[345]6789");
+ verifyDeleteSurroundingTextMain("012[345]6789", -1, -1, "012[345]6789");
+ verifyDeleteSurroundingTextMain("012[345]6789", 1, 2, "01[345]89");
+ verifyDeleteSurroundingTextMain("012[345]6789", 10, 1, "[345]789");
+ verifyDeleteSurroundingTextMain("012[345]6789", 1, 10, "01[345]");
+ verifyDeleteSurroundingTextMain("[012]3456789", 3, 3, "[012]6789");
+ verifyDeleteSurroundingTextMain("0123456[789]", 3, 3, "0123[789]");
+ verifyDeleteSurroundingTextMain("[0123456789]", 0, 0, "[0123456789]");
+ verifyDeleteSurroundingTextMain("[0123456789]", 1, 1, "[0123456789]");
+
+ // Surrogate characters do not have any special meanings. Validating the character sequence
+ // is beyond the goal of this API.
+ verifyDeleteSurroundingTextMain("0<>[]3456789", 1, 0, "0<[]3456789");
+ verifyDeleteSurroundingTextMain("0<>[]3456789", 2, 0, "0[]3456789");
+ verifyDeleteSurroundingTextMain("0<>[]3456789", 3, 0, "[]3456789");
+ verifyDeleteSurroundingTextMain("012[]<>56789", 0, 1, "012[]>56789");
+ verifyDeleteSurroundingTextMain("012[]<>56789", 0, 2, "012[]56789");
+ verifyDeleteSurroundingTextMain("012[]<>56789", 0, 3, "012[]6789");
+ verifyDeleteSurroundingTextMain("0<<[]3456789", 1, 0, "0<[]3456789");
+ verifyDeleteSurroundingTextMain("0<<[]3456789", 2, 0, "0[]3456789");
+ verifyDeleteSurroundingTextMain("0<<[]3456789", 3, 0, "[]3456789");
+ verifyDeleteSurroundingTextMain("012[]<<56789", 0, 1, "012[]<56789");
+ verifyDeleteSurroundingTextMain("012[]<<56789", 0, 2, "012[]56789");
+ verifyDeleteSurroundingTextMain("012[]<<56789", 0, 3, "012[]6789");
+ verifyDeleteSurroundingTextMain("0>>[]3456789", 1, 0, "0>[]3456789");
+ verifyDeleteSurroundingTextMain("0>>[]3456789", 2, 0, "0[]3456789");
+ verifyDeleteSurroundingTextMain("0>>[]3456789", 3, 0, "[]3456789");
+ verifyDeleteSurroundingTextMain("012[]>>56789", 0, 1, "012[]>56789");
+ verifyDeleteSurroundingTextMain("012[]>>56789", 0, 2, "012[]56789");
+ verifyDeleteSurroundingTextMain("012[]>>56789", 0, 3, "012[]6789");
+ }
+
+ private void verifyDeleteSurroundingTextInCodePointsMain(final String initialState,
+ final int deleteBeforeInCodePoints, final int deleteAfterInCodePoints,
+ final String expectedState) {
+ final CharSequence source = InputConnectionTestUtils.formatString(initialState);
+ final BaseInputConnection ic = createDummyConnectionWithSelection(mView, source);
+ ic.deleteSurroundingTextInCodePoints(deleteBeforeInCodePoints, deleteAfterInCodePoints);
+
+ final CharSequence expectedString = InputConnectionTestUtils.formatString(expectedState);
+ final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
+ final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
+
+ // It is sufficient to check the surrounding text up to source.length() characters, because
+ // InputConnection.deleteSurroundingTextInCodePoints() is not supposed to increase the text
+ // length.
+ final int retrievalLength = source.length();
+ if (expectedSelectionStart == 0) {
+ assertTrue(TextUtils.isEmpty(ic.getTextBeforeCursor(retrievalLength, 0)));
+ } else {
+ assertEquals(expectedString.subSequence(0, expectedSelectionStart).toString(),
+ ic.getTextBeforeCursor(retrievalLength, 0).toString());
+ }
+ if (expectedSelectionStart == expectedSelectionEnd) {
+ assertTrue(TextUtils.isEmpty(ic.getSelectedText(0))); // null is allowed.
+ } else {
+ assertEquals(expectedString.subSequence(expectedSelectionStart,
+ expectedSelectionEnd).toString(), ic.getSelectedText(0).toString());
+ }
+ if (expectedSelectionEnd == expectedString.length()) {
+ assertTrue(TextUtils.isEmpty(ic.getTextAfterCursor(retrievalLength, 0)));
+ } else {
+ assertEquals(expectedString.subSequence(expectedSelectionEnd,
+ expectedString.length()).toString(),
+ ic.getTextAfterCursor(retrievalLength, 0).toString());
+ }
+ }
+
+ /**
+ * Tests {@link BaseInputConnection#deleteSurroundingTextInCodePoints(int, int)}
+ * comprehensively.
+ */
+ @Test
+ public void testDeleteSurroundingTextInCodePoints() throws Throwable {
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 0, 0, "012[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", -1, -1, "012[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 1, 2, "01[]56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 10, 1, "[]456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 1, 10, "01[]");
+ verifyDeleteSurroundingTextInCodePointsMain("[]0123456789", 3, 3, "[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0123456789[]", 3, 3, "0123456[]");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 0, 0, "012[345]6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", -1, -1, "012[345]6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 1, 2, "01[345]89");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 10, 1, "[345]789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 1, 10, "01[345]");
+ verifyDeleteSurroundingTextInCodePointsMain("[012]3456789", 3, 3, "[012]6789");
+ verifyDeleteSurroundingTextInCodePointsMain("0123456[789]", 3, 3, "0123[789]");
+ verifyDeleteSurroundingTextInCodePointsMain("[0123456789]", 0, 0, "[0123456789]");
+ verifyDeleteSurroundingTextInCodePointsMain("[0123456789]", 1, 1, "[0123456789]");
+
+ verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 1, 0, "0[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 2, 0, "[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 3, 0, "[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 1, "012[]56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 2, "012[]6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 3, "012[]789");
+
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 0, "[]<><><><><>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 1, "[]<><><><>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 2, "[]<><><>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 3, "[]<><>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 4, "[]<>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 5, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 6, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 1000, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 0, 0, "<><><><><>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 1, 0, "<><><><>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 2, 0, "<><><>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 3, 0, "<><>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 4, 0, "<>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 5, 0, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 6, 0, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 1000, 0, "[]");
+
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 1, 0, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 2, 0, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 3, 0, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 1, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 2, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 3, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 1, 0, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 2, 0, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 3, 0, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 1, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 2, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 3, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 1, 0, "01<[]>456789");
+ verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 0, 1, "01<[]>456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 1, 0, "<1[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 2, 0, "<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 3, 0, "<12[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 1, 0, "<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 2, 0, "<<>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 3, 0, "<<>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 1, "012[]4>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 2, "012[]>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 3, "012[]34>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 1, "012[]>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 2, "012[]<>>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 3, "012[]<>>6789");
+
+ // Atomicity test.
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 1, 1, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 2, 1, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 3, 1, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 1, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 2, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 3, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 1, 1, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 2, 1, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 3, 1, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 1, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 2, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 3, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 1, 1, "01<[]>456789");
+
+ // Do not verify the character sequences in the selected region.
+ verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 0, "0[><]456789");
+ verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 0, 1, "01[><]56789");
+ verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 1, "0[><]56789");
+ }
+
+ @Test
+ public void testCloseConnection() {
+ final CharSequence source = "0123456789";
+ mConnection.commitText(source, source.length());
+ final Editable text = mConnection.getEditable();
+ BaseInputConnection.setComposingSpans(text, 2, 5);
+ assertEquals(2, BaseInputConnection.getComposingSpanStart(text));
+ assertEquals(5, BaseInputConnection.getComposingSpanEnd(text));
+
+ // BaseInputConnection#closeConnection() must clear the on-going composition.
+ mConnection.closeConnection();
+ assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
+ assertEquals(-1, BaseInputConnection.getComposingSpanEnd(text));
+ }
+
+ @Test
+ public void testGetHandler() {
+ // BaseInputConnection must not implement getHandler().
+ assertNull(mConnection.getHandler());
+ }
+
+ @Test
+ public void testCommitContent() {
+ final InputContentInfo inputContentInfo = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ // The default implementation should do nothing and just return false.
+ assertFalse(mConnection.commitContent(inputContentInfo, 0 /* flags */, null /* opts */));
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/CompletionInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/CompletionInfoTest.java
new file mode 100644
index 0000000..9a8d206
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/CompletionInfoTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.view.inputmethod.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.CompletionInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CompletionInfoTest {
+ private static final int ID = 1;
+ private static final int POSITION = 1;
+ private static final String TEXT = "CompletionInfoText";
+ private static final String LABEL = "CompletionInfoLabel";
+
+ @Test
+ public void testCompletionInfo() {
+ new CompletionInfo(ID, POSITION, TEXT);
+ CompletionInfo info = new CompletionInfo(ID, POSITION, TEXT, LABEL);
+ assertCompletionInfo(info);
+
+ assertEquals(0, info.describeContents());
+ assertNotNull(info.toString());
+
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ CompletionInfo targetInfo = CompletionInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+ assertCompletionInfo(targetInfo);
+ }
+
+ private void assertCompletionInfo(CompletionInfo info) {
+ assertEquals(ID, info.getId());
+ assertEquals(POSITION, info.getPosition());
+ assertEquals(TEXT, info.getText().toString());
+ assertEquals(LABEL, info.getLabel().toString());
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
new file mode 100644
index 0000000..1557511
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2009 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.view.inputmethod.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.MoreAsserts;
+import android.text.TextUtils;
+import android.util.Printer;
+import android.view.inputmethod.EditorInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class EditorInfoTest {
+ @Test
+ public void testEditorInfo() {
+ EditorInfo info = new EditorInfo();
+
+ info.actionId = 1;
+ info.actionLabel = "actionLabel";
+ info.fieldId = 2;
+ info.fieldName = "fieldName";
+ info.hintText = "hintText";
+ info.imeOptions = EditorInfo.IME_FLAG_NO_ENTER_ACTION;
+ info.initialCapsMode = TextUtils.CAP_MODE_CHARACTERS;
+ info.initialSelEnd = 10;
+ info.initialSelStart = 0;
+ info.inputType = EditorInfo.TYPE_MASK_CLASS;
+ info.label = "label";
+ info.packageName = "android.view.cts";
+ info.privateImeOptions = "privateIme";
+ Bundle b = new Bundle();
+ String key = "bundleKey";
+ String value = "bundleValue";
+ b.putString(key, value);
+ info.extras = b;
+ info.hintLocales = LocaleList.forLanguageTags("en-PH,en-US");
+ info.contentMimeTypes = new String[]{"image/gif", "image/png"};
+
+ assertEquals(0, info.describeContents());
+
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ EditorInfo targetInfo = EditorInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+ assertEquals(info.actionId, targetInfo.actionId);
+ assertEquals(info.fieldId, targetInfo.fieldId);
+ assertEquals(info.fieldName, targetInfo.fieldName);
+ assertEquals(info.imeOptions, targetInfo.imeOptions);
+ assertEquals(info.initialCapsMode, targetInfo.initialCapsMode);
+ assertEquals(info.initialSelEnd, targetInfo.initialSelEnd);
+ assertEquals(info.initialSelStart, targetInfo.initialSelStart);
+ assertEquals(info.inputType, targetInfo.inputType);
+ assertEquals(info.packageName, targetInfo.packageName);
+ assertEquals(info.privateImeOptions, targetInfo.privateImeOptions);
+ assertEquals(info.hintText.toString(), targetInfo.hintText.toString());
+ assertEquals(info.actionLabel.toString(), targetInfo.actionLabel.toString());
+ assertEquals(info.label.toString(), targetInfo.label.toString());
+ assertEquals(info.extras.getString(key), targetInfo.extras.getString(key));
+ assertEquals(info.hintLocales, targetInfo.hintLocales);
+ MoreAsserts.assertEquals(info.contentMimeTypes, targetInfo.contentMimeTypes);
+
+ Printer printer = mock(Printer.class);
+ String prefix = "TestEditorInfo";
+ info.dump(printer, prefix);
+ verify(printer, atLeastOnce()).println(anyString());
+ }
+
+ @Test
+ public void testNullHintLocals() {
+ EditorInfo info = new EditorInfo();
+ info.hintLocales = null;
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ EditorInfo targetInfo = EditorInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+ assertNull(targetInfo.hintLocales);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextRequestTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextRequestTest.java
new file mode 100644
index 0000000..3e12579
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextRequestTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2009 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.view.inputmethod.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.ExtractedTextRequest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExtractedTextRequestTest {
+ @Test
+ public void testExtractedTextRequest() {
+ ExtractedTextRequest request = new ExtractedTextRequest();
+ request.flags = 1;
+ request.hintMaxChars = 100;
+ request.hintMaxLines = 10;
+ request.token = 2;
+
+ assertEquals(0, request.describeContents());
+
+ Parcel p = Parcel.obtain();
+ request.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ExtractedTextRequest target = ExtractedTextRequest.CREATOR.createFromParcel(p);
+ p.recycle();
+ assertEquals(request.flags, target.flags);
+ assertEquals(request.hintMaxChars, request.hintMaxChars);
+ assertEquals(request.hintMaxLines, target.hintMaxLines);
+ assertEquals(request.token, target.token);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
new file mode 100644
index 0000000..41e3efa
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.ExtractedText;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExtractedTextTest {
+ @Test
+ public void testWriteToParcel() {
+ ExtractedText extractedText = new ExtractedText();
+ extractedText.flags = 1;
+ extractedText.selectionEnd = 11;
+ extractedText.selectionStart = 2;
+ extractedText.startOffset = 1;
+ CharSequence text = "test";
+ extractedText.text = text;
+ Parcel p = Parcel.obtain();
+ extractedText.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ExtractedText target = ExtractedText.CREATOR.createFromParcel(p);
+ assertEquals(extractedText.flags, target.flags);
+ assertEquals(extractedText.selectionEnd, target.selectionEnd);
+ assertEquals(extractedText.selectionStart, target.selectionStart);
+ assertEquals(extractedText.startOffset, target.startOffset);
+ assertEquals(extractedText.partialStartOffset, target.partialStartOffset);
+ assertEquals(extractedText.partialEndOffset, target.partialEndOffset);
+ assertEquals(extractedText.text.toString(), target.text.toString());
+
+ assertEquals(0, extractedText.describeContents());
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java
new file mode 100644
index 0000000..faaff3d
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import android.os.Binder;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.InputBinding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputBindingTest {
+ @Test
+ public void testInputBinding() {
+ View view = new View(InstrumentationRegistry.getTargetContext());
+ BaseInputConnection bic = new BaseInputConnection(view, false);
+ Binder binder = new Binder();
+ int uid = 1;
+ int pid = 2;
+ InputBinding inputBinding = new InputBinding(bic, binder, uid, pid);
+ new InputBinding(bic, inputBinding);
+ assertSame(bic, inputBinding.getConnection());
+ assertSame(binder, inputBinding.getConnectionToken());
+ assertEquals(uid, inputBinding.getUid());
+ assertEquals(pid, inputBinding.getPid());
+
+ assertNotNull(inputBinding.toString());
+ assertEquals(0, inputBinding.describeContents());
+
+ Parcel p = Parcel.obtain();
+ inputBinding.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ InputBinding target = InputBinding.CREATOR.createFromParcel(p);
+ assertEquals(uid, target.getUid());
+ assertEquals(pid, target.getPid());
+ assertSame(binder, target.getConnectionToken());
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
new file mode 100644
index 0000000..71abacc
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2009 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.view.inputmethod.cts;
+
+import static com.android.compatibility.common.util.WidgetTestUtils.sameCharSequence;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputContentInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputConnectionWrapperTest {
+ @Test
+ public void testInputConnectionWrapper() {
+ InputConnection inputConnection = mock(InputConnection.class);
+ doReturn(true).when(inputConnection).commitContent(any(InputContentInfo.class),
+ anyInt(), any(Bundle.class));
+ InputConnectionWrapper wrapper = new InputConnectionWrapper(null, true);
+ try {
+ wrapper.beginBatchEdit();
+ fail("Failed to throw NullPointerException!");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ wrapper.setTarget(inputConnection);
+
+ wrapper.beginBatchEdit();
+ verify(inputConnection, times(1)).beginBatchEdit();
+
+ wrapper.clearMetaKeyStates(KeyEvent.META_ALT_ON);
+ verify(inputConnection, times(1)).clearMetaKeyStates(KeyEvent.META_ALT_ON);
+
+ wrapper.commitCompletion(new CompletionInfo(1, 1, "testText"));
+ ArgumentCaptor<CompletionInfo> completionInfoCaptor =
+ ArgumentCaptor.forClass(CompletionInfo.class);
+ verify(inputConnection, times(1)).commitCompletion(completionInfoCaptor.capture());
+ assertEquals(1, completionInfoCaptor.getValue().getId());
+ assertEquals(1, completionInfoCaptor.getValue().getPosition());
+ assertEquals("testText", completionInfoCaptor.getValue().getText());
+
+ wrapper.commitCorrection(new CorrectionInfo(0, "oldText", "newText"));
+ ArgumentCaptor<CorrectionInfo> correctionInfoCaptor =
+ ArgumentCaptor.forClass(CorrectionInfo.class);
+ verify(inputConnection, times(1)).commitCorrection(correctionInfoCaptor.capture());
+ assertEquals(0, correctionInfoCaptor.getValue().getOffset());
+ assertEquals("oldText", correctionInfoCaptor.getValue().getOldText());
+ assertEquals("newText", correctionInfoCaptor.getValue().getNewText());
+
+ wrapper.commitText("Text", 1);
+ verify(inputConnection, times(1)).commitText(sameCharSequence("Text"), eq(1));
+
+ wrapper.deleteSurroundingText(10, 100);
+ verify(inputConnection, times(1)).deleteSurroundingText(10, 100);
+
+ wrapper.deleteSurroundingTextInCodePoints(10, 100);
+ verify(inputConnection, times(1)).deleteSurroundingTextInCodePoints(10, 100);
+
+ wrapper.endBatchEdit();
+ verify(inputConnection, times(1)).endBatchEdit();
+
+ wrapper.finishComposingText();
+ verify(inputConnection, times(1)).finishComposingText();
+
+ wrapper.getCursorCapsMode(TextUtils.CAP_MODE_CHARACTERS);
+ verify(inputConnection, times(1)).getCursorCapsMode(TextUtils.CAP_MODE_CHARACTERS);
+
+ wrapper.getExtractedText(new ExtractedTextRequest(), 0);
+ verify(inputConnection, times(1)).getExtractedText(any(ExtractedTextRequest.class), eq(0));
+
+ wrapper.getTextAfterCursor(5, 0);
+ verify(inputConnection, times(1)).getTextAfterCursor(5, 0);
+
+ wrapper.getTextBeforeCursor(3, 0);
+ verify(inputConnection, times(1)).getTextBeforeCursor(3, 0);
+
+ wrapper.performContextMenuAction(1);
+ verify(inputConnection, times(1)).performContextMenuAction(1);
+
+ wrapper.performEditorAction(EditorInfo.IME_ACTION_GO);
+ verify(inputConnection, times(1)).performEditorAction(EditorInfo.IME_ACTION_GO);
+
+ wrapper.performPrivateCommand("com.android.action.MAIN", new Bundle());
+ verify(inputConnection, times(1)).performPrivateCommand(eq("com.android.action.MAIN"),
+ any(Bundle.class));
+
+ wrapper.reportFullscreenMode(true);
+ verify(inputConnection, times(1)).reportFullscreenMode(true);
+
+ wrapper.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0));
+ ArgumentCaptor<KeyEvent> keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class);
+ verify(inputConnection, times(1)).sendKeyEvent(keyEventCaptor.capture());
+ assertEquals(KeyEvent.ACTION_DOWN, keyEventCaptor.getValue().getAction());
+ assertEquals(KeyEvent.KEYCODE_0, keyEventCaptor.getValue().getKeyCode());
+
+ wrapper.setComposingText("Text", 1);
+ verify(inputConnection, times(1)).setComposingText("Text", 1);
+
+ wrapper.setSelection(0, 10);
+ verify(inputConnection, times(1)).setSelection(0, 10);
+
+ wrapper.getSelectedText(0);
+ verify(inputConnection, times(1)).getSelectedText(0);
+
+ wrapper.setComposingRegion(0, 3);
+ verify(inputConnection, times(1)).setComposingRegion(0, 3);
+
+ wrapper.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE);
+ verify(inputConnection, times(1))
+ .requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE);
+
+ wrapper.closeConnection();
+ verify(inputConnection, times(1)).closeConnection();
+
+ verify(inputConnection, never()).getHandler();
+ assertNull(wrapper.getHandler());
+ verify(inputConnection, times(1)).getHandler();
+
+ verify(inputConnection, never()).commitContent(any(InputContentInfo.class), anyInt(),
+ any(Bundle.class));
+
+ final InputContentInfo inputContentInfo = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ wrapper.commitContent(inputContentInfo, 0 /* flags */, null /* opt */);
+ verify(inputConnection, times(1)).commitContent(inputContentInfo, 0, null);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputContentInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputContentInfoTest.java
new file mode 100644
index 0000000..777b6d8
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputContentInfoTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.InputContentInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.InvalidParameterException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputContentInfoTest {
+ @Test
+ public void testInputContentInfo() {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+
+ assertEquals(Uri.parse("content://com.example/path"), info.getContentUri());
+ assertEquals(1, info.getDescription().getMimeTypeCount());
+ assertEquals("image/png", info.getDescription().getMimeType(0));
+ assertEquals("sample content", info.getDescription().getLabel());
+ assertEquals(Uri.parse("https://example.com"), info.getLinkUri());
+ assertEquals(0, info.describeContents());
+
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ InputContentInfo targetInfo = InputContentInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+
+ assertEquals(info.getContentUri(), targetInfo.getContentUri());
+ assertEquals(info.getDescription().getMimeTypeCount(),
+ targetInfo.getDescription().getMimeTypeCount());
+ assertEquals(info.getDescription().getMimeType(0),
+ targetInfo.getDescription().getMimeType(0));
+ assertEquals(info.getDescription().getLabel(), targetInfo.getDescription().getLabel());
+ assertEquals(info.getLinkUri(), targetInfo.getLinkUri());
+ assertEquals(info.describeContents(), targetInfo.describeContents());
+ }
+
+ @Test
+ public void testOptionalConstructorParam() {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}));
+
+ assertEquals(Uri.parse("content://com.example/path"), info.getContentUri());
+ assertEquals(1, info.getDescription().getMimeTypeCount());
+ assertEquals("image/png", info.getDescription().getMimeType(0));
+ assertEquals("sample content", info.getDescription().getLabel());
+ assertNull(info.getLinkUri());
+ assertEquals(0, info.describeContents());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testContentUriNullContentUri() {
+ new InputContentInfo(
+ null, new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ }
+
+ @Test(expected = InvalidParameterException.class)
+ public void testContentUriInvalidContentUri() {
+ new InputContentInfo(
+ Uri.parse("https://example.com"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testMimeTypeNulLDescription() {
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"), null,
+ Uri.parse("https://example.com"));
+ }
+
+ @Test
+ public void testLinkUri() {
+ // Test that we accept null link Uri
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ null);
+
+ // Test that we accept http link Uri
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("http://example.com/path"));
+
+ // Test that we accept https link Uri
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com/path"));
+ }
+
+ @Test(expected = InvalidParameterException.class)
+ public void testLinkUriFtpLinkUri() {
+ // InputContentInfo must accept http and https link Uri only
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("ftp://example.com/path"));
+ }
+
+ @Test(expected = InvalidParameterException.class)
+ public void testLinkUriContentLinkUri() {
+ // InputContentInfo must accept http and https link Uri only
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("content://com.example/path"));
+ }
+
+ @Test
+ public void testRequestAndReleasePermission() {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+
+ // Here we only assert that {request, release}Permission() do not crash, because ensuring
+ // the entire functionality of these methods requires end-to-end IME test environment, which
+ // we do not have yet in CTS.
+ // Note it is actually intentional that calling these methods here has no effect. Those
+ // methods would have effect only after the object is passed from the IME process to the
+ // application process.
+ // TODO: Create an end-to-end CTS test for this functionality.
+ info.requestPermission();
+ info.releasePermission();
+ info.requestPermission();
+ info.releasePermission();
+ }
+
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java
new file mode 100644
index 0000000..9501d44
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.inputmethod.cts.R;
+
+public class InputMethodCtsActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.inputmethod_edittext);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
new file mode 100644
index 0000000..b34673be
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.util.Printer;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodInfoTest {
+ private Context mContext;
+
+ private InputMethodInfo mInputMethodInfo;
+ private String mPackageName;
+ private String mClassName;
+ private CharSequence mLabel;
+ private String mSettingsActivity;
+
+ private int mSubtypeNameResId;
+ private int mSubtypeIconResId;
+ private String mSubtypeLocale;
+ private String mSubtypeMode;
+ private String mSubtypeExtraValue_key;
+ private String mSubtypeExtraValue_value;
+ private String mSubtypeExtraValue;
+ private boolean mSubtypeIsAuxiliary;
+ private boolean mSubtypeOverridesImplicitlyEnabledSubtype;
+ private int mSubtypeId;
+ private InputMethodSubtype mInputMethodSubtype;
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mPackageName = mContext.getPackageName();
+ mClassName = InputMethodSettingsActivityStub.class.getName();
+ mLabel = "test";
+ mSettingsActivity = "android.view.inputmethod.cts.InputMethodSettingsActivityStub";
+ mInputMethodInfo = new InputMethodInfo(mPackageName, mClassName, mLabel, mSettingsActivity);
+
+ mSubtypeNameResId = 0;
+ mSubtypeIconResId = 0;
+ mSubtypeLocale = "en_US";
+ mSubtypeMode = "keyboard";
+ mSubtypeExtraValue_key = "key1";
+ mSubtypeExtraValue_value = "value1";
+ mSubtypeExtraValue = "tag," + mSubtypeExtraValue_key + "=" + mSubtypeExtraValue_value;
+ mSubtypeIsAuxiliary = false;
+ mSubtypeOverridesImplicitlyEnabledSubtype = false;
+ mSubtypeId = 99;
+ mInputMethodSubtype = new InputMethodSubtype(mSubtypeNameResId, mSubtypeIconResId,
+ mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue, mSubtypeIsAuxiliary,
+ mSubtypeOverridesImplicitlyEnabledSubtype, mSubtypeId);
+ }
+
+ @Test
+ public void testInputMethodInfoProperties() throws XmlPullParserException, IOException {
+ assertEquals(0, mInputMethodInfo.describeContents());
+ assertNotNull(mInputMethodInfo.toString());
+
+ assertInfo(mInputMethodInfo);
+ assertEquals(0, mInputMethodInfo.getIsDefaultResourceId());
+
+ Intent intent = new Intent(InputMethod.SERVICE_INTERFACE);
+ intent.setClass(mContext, InputMethodSettingsActivityStub.class);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> ris = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
+ for (int i = 0; i < ris.size(); i++) {
+ ResolveInfo resolveInfo = ris.get(i);
+ mInputMethodInfo = new InputMethodInfo(mContext, resolveInfo);
+ assertService(resolveInfo.serviceInfo, mInputMethodInfo.getServiceInfo());
+ assertInfo(mInputMethodInfo);
+ }
+ }
+
+ @Test
+ public void testInputMethodSubtypeProperties() {
+ // TODO: Test InputMethodSubtype.getDisplayName()
+ assertEquals(mSubtypeNameResId, mInputMethodSubtype.getNameResId());
+ assertEquals(mSubtypeIconResId, mInputMethodSubtype.getIconResId());
+ assertEquals(mSubtypeLocale, mInputMethodSubtype.getLocale());
+ assertEquals(mSubtypeMode, mInputMethodSubtype.getMode());
+ assertEquals(mSubtypeExtraValue, mInputMethodSubtype.getExtraValue());
+ assertTrue(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValue_key));
+ assertEquals(mSubtypeExtraValue_value,
+ mInputMethodSubtype.getExtraValueOf(mSubtypeExtraValue_key));
+ assertEquals(mSubtypeIsAuxiliary, mInputMethodSubtype.isAuxiliary());
+ assertEquals(mSubtypeOverridesImplicitlyEnabledSubtype,
+ mInputMethodSubtype.overridesImplicitlyEnabledSubtype());
+ assertEquals(mSubtypeId, mInputMethodSubtype.hashCode());
+ }
+
+ private void assertService(ServiceInfo expected, ServiceInfo actual) {
+ assertEquals(expected.getIconResource(), actual.getIconResource());
+ assertEquals(expected.labelRes, actual.labelRes);
+ assertEquals(expected.nonLocalizedLabel, actual.nonLocalizedLabel);
+ assertEquals(expected.icon, actual.icon);
+ assertEquals(expected.permission, actual.permission);
+ }
+
+ private void assertInfo(InputMethodInfo info) {
+ assertEquals(mPackageName, info.getPackageName());
+ assertEquals(mSettingsActivity, info.getSettingsActivity());
+ ComponentName component = info.getComponent();
+ assertEquals(mClassName, component.getClassName());
+ String expectedId = component.flattenToShortString();
+ assertEquals(expectedId, info.getId());
+ assertEquals(mClassName, info.getServiceName());
+ }
+
+ @Test
+ public void testDump() {
+ Printer printer = mock(Printer.class);
+ String prefix = "test";
+ mInputMethodInfo.dump(printer, prefix);
+ verify(printer, atLeastOnce()).println(anyString());
+ }
+
+ @Test
+ public void testLoadIcon() {
+ PackageManager pm = mContext.getPackageManager();
+ assertNotNull(mInputMethodInfo.loadIcon(pm));
+ }
+
+ @Test
+ public void testEquals() {
+ InputMethodInfo inputMethodInfo = new InputMethodInfo(mPackageName, mClassName, mLabel,
+ mSettingsActivity);
+ assertTrue(inputMethodInfo.equals(mInputMethodInfo));
+ }
+
+ @Test
+ public void testLoadLabel() {
+ CharSequence expected = "test";
+ PackageManager pm = mContext.getPackageManager();
+ assertEquals(expected.toString(), mInputMethodInfo.loadLabel(pm).toString());
+ }
+
+ @Test
+ public void testInputMethodInfoWriteToParcel() {
+ final Parcel p = Parcel.obtain();
+ mInputMethodInfo.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ final InputMethodInfo imi = InputMethodInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+
+ assertEquals(mInputMethodInfo.getPackageName(), imi.getPackageName());
+ assertEquals(mInputMethodInfo.getServiceName(), imi.getServiceName());
+ assertEquals(mInputMethodInfo.getSettingsActivity(), imi.getSettingsActivity());
+ assertEquals(mInputMethodInfo.getId(), imi.getId());
+ assertEquals(mInputMethodInfo.getIsDefaultResourceId(), imi.getIsDefaultResourceId());
+ assertService(mInputMethodInfo.getServiceInfo(), imi.getServiceInfo());
+ }
+
+ @Test
+ public void testInputMethodSubtypeWriteToParcel() {
+ final Parcel p = Parcel.obtain();
+ mInputMethodSubtype.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ final InputMethodSubtype subtype = InputMethodSubtype.CREATOR.createFromParcel(p);
+ p.recycle();
+
+ assertEquals(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValue_key),
+ subtype.containsExtraValueKey(mSubtypeExtraValue_key));
+ assertEquals(mInputMethodSubtype.getExtraValue(), subtype.getExtraValue());
+ assertEquals(mInputMethodSubtype.getExtraValueOf(mSubtypeExtraValue_key),
+ subtype.getExtraValueOf(mSubtypeExtraValue_key));
+ assertEquals(mInputMethodSubtype.getIconResId(), subtype.getIconResId());
+ assertEquals(mInputMethodSubtype.getLocale(), subtype.getLocale());
+ assertEquals(mInputMethodSubtype.getMode(), subtype.getMode());
+ assertEquals(mInputMethodSubtype.getNameResId(), subtype.getNameResId());
+ assertEquals(mInputMethodSubtype.hashCode(), subtype.hashCode());
+ assertEquals(mInputMethodSubtype.isAuxiliary(), subtype.isAuxiliary());
+ assertEquals(mInputMethodSubtype.overridesImplicitlyEnabledSubtype(),
+ subtype.overridesImplicitlyEnabledSubtype());
+ }
+
+ @Test
+ public void testInputMethodSubtypesOfSystemImes() {
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS)) {
+ return;
+ }
+
+ final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+ final List<InputMethodInfo> imis = imm.getInputMethodList();
+ final ArrayList<String> localeList = new ArrayList<>(Arrays.asList(
+ Resources.getSystem().getAssets().getLocales()));
+ boolean foundEnabledSystemImeSubtypeWithValidLanguage = false;
+ for (InputMethodInfo imi : imis) {
+ if ((imi.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ continue;
+ }
+ final int subtypeCount = imi.getSubtypeCount();
+ // System IME must have one subtype at least.
+ assertTrue(subtypeCount > 0);
+ if (foundEnabledSystemImeSubtypeWithValidLanguage) {
+ continue;
+ }
+ final List<InputMethodSubtype> enabledSubtypes =
+ imm.getEnabledInputMethodSubtypeList(imi, true);
+ SUBTYPE_LOOP:
+ for (InputMethodSubtype subtype : enabledSubtypes) {
+ final String subtypeLocale = subtype.getLocale();
+ if (subtypeLocale.length() < 2) {
+ continue;
+ }
+ // TODO: Detect language more strictly.
+ final String subtypeLanguage = subtypeLocale.substring(0, 2);
+ for (final String locale : localeList) {
+ if (locale.startsWith(subtypeLanguage)) {
+ foundEnabledSystemImeSubtypeWithValidLanguage = true;
+ break SUBTYPE_LOOP;
+ }
+ }
+ }
+ }
+ assertTrue(foundEnabledSystemImeSubtypeWithValidLanguage);
+ }
+
+ @Test
+ public void testAtLeastOneEncryptionAwareInputMethodIsAvailable() {
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS)) {
+ return;
+ }
+
+ if (!TextUtils.equals("native", getFbeMode())) {
+ // Skip the test unless the device is in native FBE mode.
+ return;
+ }
+
+ final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+ final List<InputMethodInfo> imis = imm.getInputMethodList();
+ boolean hasEncryptionAwareInputMethod = false;
+ for (final InputMethodInfo imi : imis) {
+ final ServiceInfo serviceInfo = imi.getServiceInfo();
+ if (serviceInfo == null) {
+ continue;
+ }
+ if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) !=
+ ApplicationInfo.FLAG_SYSTEM) {
+ continue;
+ }
+ if (serviceInfo.encryptionAware) {
+ hasEncryptionAwareInputMethod = true;
+ break;
+ }
+ }
+ assertTrue(hasEncryptionAwareInputMethod);
+ }
+
+ private String getFbeMode() {
+ try (ParcelFileDescriptor.AutoCloseInputStream in =
+ new ParcelFileDescriptor.AutoCloseInputStream(InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .executeShellCommand("sm get-fbe-mode"))) {
+ try (BufferedReader br =
+ new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
+ // Assume that the output of "sm get-fbe-mode" is always one-line.
+ final String line = br.readLine();
+ return line != null ? line.trim() : "";
+ }
+ } catch (IOException e) {
+ return "";
+ }
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
new file mode 100644
index 0000000..16eaad0
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
+import android.view.Window;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.R;
+import android.widget.EditText;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodManagerTest {
+ private Instrumentation mInstrumentation;
+ private InputMethodCtsActivity mActivity;
+
+ @Rule
+ public ActivityTestRule<InputMethodCtsActivity> mActivityRule =
+ new ActivityTestRule<>(InputMethodCtsActivity.class);
+
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @After
+ public void teardown() {
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+ }
+
+ @Test
+ public void testInputMethodManager() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS)) {
+ return;
+ }
+
+ Window window = mActivity.getWindow();
+ final EditText view = (EditText) window.findViewById(R.id.entry);
+
+ PollingCheck.waitFor(1000, view::hasWindowFocus);
+
+ mActivityRule.runOnUiThread(view::requestFocus);
+ mInstrumentation.waitForIdleSync();
+ assertTrue(view.isFocused());
+
+ BaseInputConnection connection = new BaseInputConnection(view, false);
+ Context context = mInstrumentation.getTargetContext();
+ final InputMethodManager imManager = (InputMethodManager) context
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ PollingCheck.waitFor(imManager::isActive);
+
+ assertTrue(imManager.isAcceptingText());
+ assertTrue(imManager.isActive(view));
+
+ assertFalse(imManager.isFullscreenMode());
+ connection.reportFullscreenMode(true);
+ // Only IMEs are allowed to report full-screen mode. Calling this method from the
+ // application should have no effect.
+ assertFalse(imManager.isFullscreenMode());
+
+ mActivityRule.runOnUiThread(() -> {
+ IBinder token = view.getWindowToken();
+
+ // Show and hide input method.
+ assertTrue(imManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT));
+ assertTrue(imManager.hideSoftInputFromWindow(token, 0));
+
+ Handler handler = new Handler();
+ ResultReceiver receiver = new ResultReceiver(handler);
+ assertTrue(imManager.showSoftInput(view, 0, receiver));
+ receiver = new ResultReceiver(handler);
+ assertTrue(imManager.hideSoftInputFromWindow(token, 0, receiver));
+
+ imManager.showSoftInputFromInputMethod(token, InputMethodManager.SHOW_FORCED);
+ imManager.hideSoftInputFromInputMethod(token, InputMethodManager.HIDE_NOT_ALWAYS);
+
+ // status: hide to show to hide
+ imManager.toggleSoftInputFromWindow(token, 0, InputMethodManager.HIDE_NOT_ALWAYS);
+ imManager.toggleSoftInputFromWindow(token, 0, InputMethodManager.HIDE_NOT_ALWAYS);
+
+ List<InputMethodInfo> enabledImList = imManager.getEnabledInputMethodList();
+ if (enabledImList != null && enabledImList.size() > 0) {
+ imManager.setInputMethod(token, enabledImList.get(0).getId());
+ // cannot test whether setting was successful
+ }
+
+ List<InputMethodInfo> imList = imManager.getInputMethodList();
+ if (imList != null && enabledImList != null) {
+ assertTrue(imList.size() >= enabledImList.size());
+ }
+ });
+ mInstrumentation.waitForIdleSync();
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodSettingsActivityStub.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodSettingsActivityStub.java
new file mode 100644
index 0000000..58aa364
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodSettingsActivityStub.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import android.preference.PreferenceActivity;
+
+public class InputMethodSettingsActivityStub extends PreferenceActivity {
+
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java
new file mode 100644
index 0000000..3f61093
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.Keyboard.Key;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.cts.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeyboardTest {
+ @Test
+ public void testKeyOnPressedAndReleased() {
+ Key nonStickyKey = null;
+ Key stickyKey = null;
+ // Indirectly instantiate Keyboard.Key with XML resources.
+ final Keyboard keyboard =
+ new Keyboard(InstrumentationRegistry.getTargetContext(), R.xml.keyboard);
+ for (final Key key : keyboard.getKeys()) {
+ if (!key.sticky) {
+ nonStickyKey = key;
+ break;
+ }
+ }
+ for (final Key key : keyboard.getModifierKeys()) {
+ if (key.sticky) {
+ stickyKey = key;
+ break;
+ }
+ }
+
+ // Asserting existences of following keys is not the goal of this test, but this should work
+ // anyway.
+ assertNotNull(nonStickyKey);
+ assertNotNull(stickyKey);
+
+ // At first, both "pressed" and "on" must be false.
+ assertFalse(nonStickyKey.pressed);
+ assertFalse(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertFalse(stickyKey.on);
+
+ // Pressing the key must flip the "pressed" state only.
+ nonStickyKey.onPressed();
+ stickyKey.onPressed();
+ assertTrue(nonStickyKey.pressed);
+ assertTrue(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertFalse(stickyKey.on);
+
+ // Releasing the key inside the key area must flip the "pressed" state and toggle the "on"
+ // state if the key is marked as sticky.
+ nonStickyKey.onReleased(true /* inside */);
+ stickyKey.onReleased(true /* inside */);
+ assertFalse(nonStickyKey.pressed);
+ assertFalse(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertTrue(stickyKey.on); // The key state is toggled.
+
+ // Pressing the key again must flip the "pressed" state only.
+ nonStickyKey.onPressed();
+ stickyKey.onPressed();
+ assertTrue(nonStickyKey.pressed);
+ assertTrue(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertTrue(stickyKey.on);
+
+ // Releasing the key inside the key area must flip the "pressed" state and toggle the "on"
+ // state if the key is marked as sticky hence we will be back to the initial state.
+ nonStickyKey.onReleased(true /* inside */);
+ stickyKey.onReleased(true /* inside */);
+ assertFalse(nonStickyKey.pressed);
+ assertFalse(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertFalse(stickyKey.on);
+
+ // Pressing then releasing the key outside the key area must not affect the "on" state.
+ nonStickyKey.onPressed();
+ stickyKey.onPressed();
+ nonStickyKey.onReleased(false /* inside */);
+ stickyKey.onReleased(false /* inside */);
+ assertFalse(nonStickyKey.pressed);
+ assertFalse(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertFalse(stickyKey.on);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java
new file mode 100644
index 0000000..3735c33
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts.util;
+
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+
+public final class InputConnectionTestUtils {
+
+ /**
+ * A utility function to generate test string for input method APIs. There are several
+ * pre-defined meta characters that are useful for unit tests.
+ *
+ * <p>Pre-defined meta characters:</p>
+ * <dl>
+ * <dl>{@code [}</dl><dd>The text selection starts from here.</dd>
+ * <dl>{@code ]}</dl><dd>The text selection ends at here.</dd>
+ * <dl>{@code <}</dl><dd>Represents a high surrogate character.</dd>
+ * <dl>{@code >}</dl><dd>Represents a low surrogate character.</dd>
+ * </ul>
+ *
+ * <p>Examples: {@code "012[3<>67]89"} will be converted to {@ode "0123HL6789"}, where
+ * {@code "H"} and {@code "L"} indicate certain high and low surrogate characters, respectively,
+ * with selecting {@code "3HL67"}.</p>
+ *
+ * @param formatString
+ * @return A {@link CharSequence} object with text selection specified by the meta characters.
+ */
+ public static CharSequence formatString(final String formatString) {
+ final String U1F427 = "\uD83D\uDC27";
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+ int selectionStart = -1;
+ int selectionEnd = -1;
+ for (int i = 0; i < formatString.length(); ++i) {
+ final Character c = formatString.charAt(i);
+ switch (c) {
+ case '[':
+ selectionStart = builder.length();
+ break;
+ case ']':
+ selectionEnd = builder.length();
+ break;
+ case '<':
+ builder.append(U1F427.charAt(0)); // High surrogate
+ break;
+ case '>':
+ builder.append(U1F427.charAt(1)); // Low surrogate
+ break;
+ default:
+ builder.append(c);
+ break;
+ }
+ }
+ if (selectionStart < 0) {
+ throw new UnsupportedOperationException("Selection marker '[' must be specified.");
+ }
+ if (selectionEnd < 0) {
+ throw new UnsupportedOperationException("Selection marker ']' must be specified.");
+ }
+ Selection.setSelection(builder, selectionStart, selectionEnd);
+ return builder;
+ }
+}
diff --git a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
new file mode 100644
index 0000000..edc71aa
--- /dev/null
+++ b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts;
+
+import android.content.Context;
+import android.hardware.HardwareBuffer;
+import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEventCallback;
+import android.hardware.SensorManager;
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.os.MemoryFile;
+import android.util.Log;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Checks Sensor Direct Report functionality
+ *
+ * This testcase tests operation of:
+ * - SensorManager.createDirectChannel()
+ * - SensorManager.configureDirectReport()
+ * - SensorDirectChannel.*
+ * - Sensor.getHighestDirectReportRateLevel()
+ * - Sensor.isDirectChannelTypeSupported()
+ */
+public class SensorDirectReportTest extends SensorTestCase {
+ private static final String TAG = "SensorDirectReportTest";
+ // nominal rates of each rate level supported
+ private static final float RATE_NORMAL_NOMINAL = 50;
+ private static final float RATE_FAST_NOMINAL = 200;
+ private static final float RATE_VERY_FAST_NOMINAL = 800;
+
+ // actuall value is allowed to be 55% to 220% of nominal value
+ private static final float FREQ_LOWER_BOUND = 0.55f;
+ private static final float FREQ_UPPER_BOUND = 2.2f;
+
+ // sensor reading assumption
+ private static final float GRAVITY_MIN = 9.81f - 1.f;
+ private static final float GRAVITY_MAX = 9.81f + 1.f;
+ private static final float GYRO_NORM_MAX = 0.05f;
+
+ // test constants
+ private static final int REST_PERIOD_BEFORE_TEST_MILLISEC = 3000;
+ private static final int TEST_RUN_TIME_PERIOD_MILLISEC = 5000;
+ private static final int ALLOWED_SENSOR_INIT_TIME_MILLISEC = 500;
+ private static final int SENSORS_EVENT_SIZE = 104;
+ private static final int SHARED_MEMORY_SIZE = 2000 * SENSORS_EVENT_SIZE;
+ private static final float MERCY_FACTOR = 0.1f;
+
+ private boolean mNeedMemoryFile;
+ private MemoryFile mMemoryFile;
+ private boolean mNeedHardwareBuffer;
+ private HardwareBuffer mHardwareBuffer;
+ private byte[] mBuffer = new byte[SHARED_MEMORY_SIZE];
+
+ private SensorManager mSensorManager;
+ private SensorDirectChannel mChannel;
+
+ @Override
+ protected void setUp() throws Exception {
+ mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
+
+ mNeedMemoryFile = isMemoryTypeNeeded(SensorDirectChannel.TYPE_ASHMEM);
+ mNeedHardwareBuffer = isMemoryTypeNeeded(SensorDirectChannel.TYPE_HARDWARE_BUFFER);
+
+ if (mNeedMemoryFile) {
+ mMemoryFile = allocateMemoryFile();
+ }
+
+ if (mNeedHardwareBuffer) {
+ mHardwareBuffer = allocateHardwareBuffer();
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mChannel != null) {
+ mChannel.close();
+ mChannel = null;
+ }
+
+ if (mMemoryFile != null) {
+ mMemoryFile.close();
+ mMemoryFile = null;
+ }
+
+ if (mHardwareBuffer != null) {
+ mHardwareBuffer.destroy();
+ mHardwareBuffer = null;
+ }
+ }
+
+ public void testSharedMemoryAllocation() throws AssertionError {
+ assertTrue("allocating MemoryFile returned null",
+ !mNeedMemoryFile || mMemoryFile != null);
+ assertTrue("allocating HardwareBuffer returned null",
+ !mNeedHardwareBuffer || mHardwareBuffer != null);
+ }
+
+ public void testAccelerometerAshmemNormal() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_ACCELEROMETER,
+ SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.RATE_NORMAL);
+ }
+
+ public void testGyroscopeAshmemNormal() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_GYROSCOPE,
+ SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.RATE_NORMAL);
+ }
+
+ public void testMagneticFieldAshmemNormal() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_MAGNETIC_FIELD,
+ SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.RATE_NORMAL);
+ }
+
+ public void testAccelerometerAshmemFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_ACCELEROMETER,
+ SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.RATE_FAST);
+
+ }
+
+ public void testGyroscopeAshmemFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_GYROSCOPE,
+ SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.RATE_FAST);
+ }
+
+ public void testMagneticFieldAshmemFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_MAGNETIC_FIELD,
+ SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.RATE_FAST);
+ }
+
+ public void testAccelerometerAshmemVeryFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_ACCELEROMETER,
+ SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.RATE_VERY_FAST);
+
+ }
+
+ public void testGyroscopeAshmemVeryFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_GYROSCOPE,
+ SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.RATE_VERY_FAST);
+ }
+
+ public void testMagneticFieldAshmemVeryFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_MAGNETIC_FIELD,
+ SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.RATE_VERY_FAST);
+ }
+
+ public void testAccelerometerHardwareBufferNormal() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_ACCELEROMETER,
+ SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ SensorDirectChannel.RATE_NORMAL);
+ }
+
+ public void testGyroscopeHardwareBufferNormal() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_GYROSCOPE,
+ SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ SensorDirectChannel.RATE_NORMAL);
+ }
+
+ public void testMagneticFieldHardwareBufferNormal() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_MAGNETIC_FIELD,
+ SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ SensorDirectChannel.RATE_NORMAL);
+ }
+
+ public void testAccelerometerHardwareBufferFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_ACCELEROMETER,
+ SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ SensorDirectChannel.RATE_FAST);
+ }
+
+ public void testGyroscopeHardwareBufferFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_GYROSCOPE,
+ SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ SensorDirectChannel.RATE_FAST);
+ }
+
+ public void testMagneticFieldHardwareBufferFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_MAGNETIC_FIELD,
+ SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ SensorDirectChannel.RATE_FAST);
+ }
+
+ public void testAccelerometerHardwareBufferVeryFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_ACCELEROMETER,
+ SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ SensorDirectChannel.RATE_VERY_FAST);
+ }
+
+ public void testGyroscopeHardwareBufferVeryFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_GYROSCOPE,
+ SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ SensorDirectChannel.RATE_VERY_FAST);
+ }
+
+ public void testMagneticFieldHardwareBufferVeryFast() {
+ runSensorDirectReportTest(
+ Sensor.TYPE_MAGNETIC_FIELD,
+ SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ SensorDirectChannel.RATE_VERY_FAST);
+ }
+
+ private void runSensorDirectReportTest(int sensorType, int memType, int rateLevel)
+ throws AssertionError {
+ Sensor s = mSensorManager.getDefaultSensor(sensorType);
+ if (s == null
+ || s.getHighestDirectReportRateLevel() < rateLevel
+ || !s.isDirectChannelTypeSupported(memType)) {
+ return;
+ }
+
+ mChannel = null;
+ try {
+ switch(memType) {
+ case SensorDirectChannel.TYPE_ASHMEM:
+ assertTrue("MemoryFile is null", mMemoryFile != null);
+ mChannel = mSensorManager.createDirectChannel(mMemoryFile);
+ assertTrue("createDirectChannel(MemoryFile) failed", mChannel != null);
+ break;
+ case SensorDirectChannel.TYPE_HARDWARE_BUFFER:
+ assertTrue("HardwareBuffer is null", mHardwareBuffer != null);
+ mChannel = mSensorManager.createDirectChannel(mHardwareBuffer);
+ assertTrue("createDirectChannel(HardwareBuffer) failed", mChannel != null);
+ break;
+ default:
+ Log.e(TAG, "Specified illegal memory type " + memType);
+ return;
+ }
+ assertTrue("Shared memory is not formatted", isSharedMemoryFormatted(memType));
+ waitBeforeStartSensor();
+
+ int token = mSensorManager.configureDirectChannel(mChannel, s, rateLevel);
+ assertTrue("configure direct mChannel failed", token > 0);
+
+ waitSensorCollection();
+
+ //stop sensor and analyze content
+ mSensorManager.configureDirectChannel(mChannel, s, SensorDirectChannel.RATE_STOP);
+ checkSharedMemoryContent(s, memType, rateLevel, token);
+ } finally {
+ if (mChannel != null) {
+ mChannel.close();
+ mChannel = null;
+ }
+ }
+ }
+
+ private void waitBeforeStartSensor() {
+ // wait for sensor system to come to a rest after previous test to avoid flakiness.
+ try {
+ SensorCtsHelper.sleep(REST_PERIOD_BEFORE_TEST_MILLISEC, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private void waitSensorCollection() {
+ // wait for sensor system to come to a rest after previous test to avoid flakiness.
+ try {
+ SensorCtsHelper.sleep(TEST_RUN_TIME_PERIOD_MILLISEC, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private MemoryFile allocateMemoryFile() {
+ MemoryFile memFile = null;
+ try {
+ memFile = new MemoryFile("Sensor Channel", SHARED_MEMORY_SIZE);
+ } catch (IOException e) {
+ Log.e(TAG, "IOException when allocating MemoryFile");
+ }
+ return memFile;
+ }
+
+ private HardwareBuffer allocateHardwareBuffer() {
+ HardwareBuffer hardwareBuffer;
+
+ // TODO: remove this after BLOB constant is added into HardwareBuffer.
+ int BLOB = 6;
+ hardwareBuffer = HardwareBuffer.create(
+ SHARED_MEMORY_SIZE, 1 /* height */, BLOB, 1 /* layer */,
+ HardwareBuffer.USAGE0_CPU_READ | HardwareBuffer.USAGE0_GPU_DATA_BUFFER
+ | HardwareBuffer.USAGE0_SENSOR_DIRECT_DATA);
+ return hardwareBuffer;
+ }
+
+ private boolean isMemoryTypeNeeded(int memType) {
+ List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+ for (Sensor s : sensorList) {
+ if (s.isDirectChannelTypeSupported(memType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isSharedMemoryFormatted(int memType) {
+ if (memType == SensorDirectChannel.TYPE_ASHMEM) {
+ if (!readMemoryFileContent()) {
+ return false;
+ }
+ } else {
+ // Android API only support memory file read back
+ return true;
+ }
+
+ for (byte b : mBuffer) {
+ if (b != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void checkSharedMemoryContent(Sensor s, int memType, int rateLevel, int token) {
+ if (memType == SensorDirectChannel.TYPE_ASHMEM) {
+ assertTrue("read MemoryFile content failed", readMemoryFileContent());
+ } else {
+ // Android API only support memory file read back
+ return;
+ }
+
+ int offset = 0;
+ int nextSerial = 1;
+ DirectReportSensorEvent e = new DirectReportSensorEvent();
+ while (offset <= SHARED_MEMORY_SIZE - SENSORS_EVENT_SIZE) {
+ parseSensorEvent(mBuffer, offset, e);
+
+ if (e.serial == 0) {
+ // reaches end of events
+ break;
+ }
+
+ assertTrue("incorrect size " + e.size + " at offset " + offset,
+ e.size == SENSORS_EVENT_SIZE);
+ assertTrue("incorrect token " + e.token + " at offset " + offset,
+ e.token == token);
+ assertTrue("incorrect serial " + e.serial + " at offset " + offset,
+ e.serial == nextSerial);
+ assertTrue("incorrect type " + e.type + " offset " + offset,
+ e.type == s.getType());
+
+ switch(s.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ double accNorm = Math.sqrt(e.x * e.x + e.y * e.y + e.z * e.z);
+ assertTrue("incorrect gravity norm " + accNorm + " at offset " + offset,
+ accNorm < GRAVITY_MAX && accNorm > GRAVITY_MIN);
+ break;
+ case Sensor.TYPE_GYROSCOPE:
+ double gyroNorm = Math.sqrt(e.x * e.x + e.y * e.y + e.z * e.z);
+ assertTrue("gyro norm too large (" + gyroNorm + ") at offset " + offset,
+ gyroNorm < GYRO_NORM_MAX);
+ break;
+ }
+
+ ++nextSerial;
+ offset += SENSORS_EVENT_SIZE;
+ }
+
+ int nEvents = nextSerial - 1;
+ float nominalFreq = 0;
+
+ switch (rateLevel) {
+ case SensorDirectChannel.RATE_NORMAL:
+ nominalFreq = RATE_NORMAL_NOMINAL;
+ break;
+ case SensorDirectChannel.RATE_FAST:
+ nominalFreq = RATE_FAST_NOMINAL;
+ break;
+ case SensorDirectChannel.RATE_VERY_FAST:
+ nominalFreq = RATE_VERY_FAST_NOMINAL;
+ break;
+ }
+
+ if (nominalFreq != 0) {
+ int minEvents;
+ int maxEvents;
+ minEvents = (int) Math.floor(
+ nominalFreq
+ * FREQ_LOWER_BOUND
+ * (TEST_RUN_TIME_PERIOD_MILLISEC - ALLOWED_SENSOR_INIT_TIME_MILLISEC)
+ * (1 - MERCY_FACTOR)
+ / 1000);
+ maxEvents = (int) Math.ceil(
+ nominalFreq
+ * FREQ_UPPER_BOUND
+ * (TEST_RUN_TIME_PERIOD_MILLISEC - ALLOWED_SENSOR_INIT_TIME_MILLISEC)
+ * (1 + MERCY_FACTOR)
+ / 1000);
+
+ assertTrue("nEvent is " + nEvents + " not between " + minEvents + " and " + maxEvents,
+ nEvents >= minEvents && nEvents <=maxEvents);
+ }
+ }
+
+ private boolean readMemoryFileContent() {
+ try {
+ if (mMemoryFile.readBytes(mBuffer, 0, 0, SHARED_MEMORY_SIZE)
+ != SHARED_MEMORY_SIZE) {
+ Log.e(TAG, "cannot read entire MemoryFile");
+ return false;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "accessing MemoryFile cause IOException");
+ return false;
+ }
+ return true;
+ }
+
+ private class DirectReportSensorEvent {
+ int size;
+ int token;
+ int type;
+ int serial;
+ long ts;
+ float x;
+ float y;
+ float z;
+ };
+
+ // parse sensors_event_t and fill information into DirectReportSensorEvent
+ private static void parseSensorEvent(byte [] buf, int offset, DirectReportSensorEvent ev) {
+ ByteBuffer b = ByteBuffer.wrap(buf, offset, SENSORS_EVENT_SIZE);
+ b.order(ByteOrder.nativeOrder());
+
+ ev.size = b.getInt();
+ ev.token = b.getInt();
+ ev.type = b.getInt();
+ ev.serial = b.getInt();
+ ev.ts = b.getLong();
+ ev.x = b.getFloat();
+ ev.y = b.getFloat();
+ ev.z = b.getFloat();
+ }
+}
diff --git a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
index 385f62f..700aff2 100644
--- a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
+++ b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
@@ -15,10 +15,14 @@
*/
package android.animation.cts;
+import static com.android.compatibility.common.util.CtsMockitoUtils.within;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -57,6 +61,7 @@
private ObjectAnimator yAnimator;
private ObjectAnimator xAnimator;
Set<Integer> identityHashes = new HashSet<>();
+ private static final float EPSILON = 0.001f;
@Rule
public ActivityTestRule<AnimationActivity> mActivityRule =
@@ -147,6 +152,37 @@
for (int i = 0; i < listeners.length; i++) {
assertTrue(listeners[i].mEndIsCalled);
}
+
+ // Now reverse the animations and verify whether the play order is reversed.
+ for (int i = 0; i < animators.length; i++) {
+ if (i == animators.length - 1) {
+ listeners[i] = new MyListener();
+ } else {
+ final int current = i;
+ listeners[i] = new MyListener() {
+ @Override
+ public void onAnimationStart(Animator anim) {
+ super.onAnimationStart(anim);
+ // Check that the previous animator has finished.
+ assertTrue(listeners[current + 1].mEndIsCalled);
+ }
+ };
+ }
+ animators[i].removeAllListeners();
+ animators[i].addListener(listeners[i]);
+ }
+
+ mActivityRule.runOnUiThread(() -> {
+ set.reverse();
+ startLatch.countDown();
+ });
+
+ // Set timeout to 100ms, if current count reaches 0 before the timeout, startLatch.await(..)
+ // will return immediately.
+ assertTrue(startLatch.await(100, TimeUnit.MILLISECONDS));
+ assertTrue(set.isRunning());
+ assertTrue(endLatch.await(totalDuration * 2, TimeUnit.MILLISECONDS));
+
}
@Test
@@ -473,6 +509,112 @@
assertSame(animator2.getInterpolator(), clone2.getInterpolator());
}
+ /**
+ * Testing seeking in an AnimatorSet containing sequential animators.
+ */
+ @Test
+ public void testSeeking() throws Throwable {
+ final AnimatorSet set = new AnimatorSet();
+ final ValueAnimator a1 = ValueAnimator.ofFloat(0f, 150f);
+ a1.setDuration(150);
+ final ValueAnimator a2 = ValueAnimator.ofFloat(150f, 250f);
+ a2.setDuration(100);
+ final ValueAnimator a3 = ValueAnimator.ofFloat(250f, 300f);
+ a3.setDuration(50);
+
+ a1.setInterpolator(null);
+ a2.setInterpolator(null);
+ a3.setInterpolator(null);
+
+ set.playSequentially(a1, a2, a3);
+
+ set.setCurrentPlayTime(100);
+ assertEquals(100f, (Float) a1.getAnimatedValue(), EPSILON);
+ assertEquals(150f, (Float) a2.getAnimatedValue(), EPSILON);
+ assertEquals(250f, (Float) a3.getAnimatedValue(), EPSILON);
+
+ set.setCurrentPlayTime(280);
+ assertEquals(150f, (Float) a1.getAnimatedValue(), EPSILON);
+ assertEquals(250f, (Float) a2.getAnimatedValue(), EPSILON);
+ assertEquals(280f, (Float) a3.getAnimatedValue(), EPSILON);
+
+ AnimatorListenerAdapter setListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ assertEquals(150f, (Float) a1.getAnimatedValue(), EPSILON);
+ assertEquals(250f, (Float) a2.getAnimatedValue(), EPSILON);
+ assertEquals(300f, (Float) a3.getAnimatedValue(), EPSILON);
+
+ }
+ };
+ AnimatorListenerAdapter mockListener = mock(AnimatorListenerAdapter.class);
+ set.addListener(setListener);
+ set.addListener(mockListener);
+ mActivityRule.runOnUiThread(() -> {
+ set.start();
+ });
+
+ verify(mockListener, within(300)).onAnimationEnd(set, false);
+
+ // Seek after a run to the middle-ish, and verify the first animator is at the end
+ // value and the 3rd at beginning value, and the 2nd animator is at the seeked value.
+ set.setCurrentPlayTime(200);
+ assertEquals(150f, (Float) a1.getAnimatedValue(), EPSILON);
+ assertEquals(200f, (Float) a2.getAnimatedValue(), EPSILON);
+ assertEquals(250f, (Float) a3.getAnimatedValue(), EPSILON);
+ }
+
+ /**
+ * Testing seeking in an AnimatorSet containing infinite animators.
+ */
+ @Test
+ public void testSeekingInfinite() {
+ final AnimatorSet set = new AnimatorSet();
+ final ValueAnimator a1 = ValueAnimator.ofFloat(0f, 100f);
+ a1.setDuration(100);
+ final ValueAnimator a2 = ValueAnimator.ofFloat(100f, 200f);
+ a2.setDuration(100);
+ a2.setRepeatCount(ValueAnimator.INFINITE);
+ a2.setRepeatMode(ValueAnimator.RESTART);
+
+ final ValueAnimator a3 = ValueAnimator.ofFloat(100f, 200f);
+ a3.setDuration(100);
+ a3.setRepeatCount(ValueAnimator.INFINITE);
+ a3.setRepeatMode(ValueAnimator.REVERSE);
+
+ a1.setInterpolator(null);
+ a2.setInterpolator(null);
+ a3.setInterpolator(null);
+ set.play(a1).before(a2);
+ set.play(a1).before(a3);
+
+ set.setCurrentPlayTime(50);
+ assertEquals(50f, (Float) a1.getAnimatedValue(), EPSILON);
+ assertEquals(100f, (Float) a2.getAnimatedValue(), EPSILON);
+ assertEquals(100f, (Float) a3.getAnimatedValue(), EPSILON);
+
+ set.setCurrentPlayTime(100);
+ assertEquals(100f, (Float) a1.getAnimatedValue(), EPSILON);
+ assertEquals(100f, (Float) a2.getAnimatedValue(), EPSILON);
+ assertEquals(100f, (Float) a3.getAnimatedValue(), EPSILON);
+
+ // Seek to the 1st iteration of the infinite repeat animators, and they should have the
+ // same value.
+ set.setCurrentPlayTime(180);
+ assertEquals(100f, (Float) a1.getAnimatedValue(), EPSILON);
+ assertEquals(180f, (Float) a2.getAnimatedValue(), EPSILON);
+ assertEquals(180f, (Float) a3.getAnimatedValue(), EPSILON);
+
+ // Seek to the 2nd iteration of the infinite repeat animators, and they should have
+ // different values as they have different repeat mode.
+ set.setCurrentPlayTime(280);
+ assertEquals(100f, (Float) a1.getAnimatedValue(), EPSILON);
+ assertEquals(180f, (Float) a2.getAnimatedValue(), EPSILON);
+ assertEquals(120f, (Float) a3.getAnimatedValue(), EPSILON);
+
+ }
+
@Test
public void testNotifiesAfterEnd() throws Throwable {
final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
@@ -498,7 +640,6 @@
animatorSet.start();
animator.end();
assertFalse(animator.isStarted());
- assertFalse(animatorSet.isStarted());
});
}
@@ -515,7 +656,7 @@
int y = 2;
}
- class MyListener extends AnimatorListenerAdapter {
+ static class MyListener extends AnimatorListenerAdapter {
boolean mStartIsCalled = false;
boolean mEndIsCalled = false;
diff --git a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
index 605e1c1..6094362 100644
--- a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
@@ -231,10 +231,12 @@
final float seekFraction = 0.2f;
final CountDownLatch frameUpdateLatch = new CountDownLatch(1);
+ final AnimatorSetTest.MyListener myListener = new AnimatorSetTest.MyListener();
final ValueAnimator anim = ValueAnimator.ofFloat(0, 1).setDuration(duration);
anim.setInterpolator(null);
final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
anim.addListener(listener);
+ anim.addListener(myListener);
mActivityRule.runOnUiThread(() -> {
anim.start();
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -256,7 +258,9 @@
anim.setCurrentPlayTime(currentPlayTime);
});
assertTrue(frameUpdateLatch.await(100, TimeUnit.MILLISECONDS));
- verify(listener, within(200)).onAnimationEnd(anim);
+ verify(listener, within(200)).onAnimationEnd(anim, false);
+ // Also make sure the onAnimationEnd(anim) is called.
+ assertTrue(myListener.mEndIsCalled);
}
@Test
@@ -309,6 +313,7 @@
@Test
public void testUpdateListeners() throws Throwable {
+ final AnimatorSetTest.MyListener myListener = new AnimatorSetTest.MyListener();
ValueAnimator.AnimatorUpdateListener l1 = mock(ValueAnimator.AnimatorUpdateListener.class);
ValueAnimator.AnimatorUpdateListener l2 = mock(ValueAnimator.AnimatorUpdateListener.class);
ValueAnimator.AnimatorUpdateListener l3 = mock(ValueAnimator.AnimatorUpdateListener.class);
@@ -327,13 +332,16 @@
a1.removeUpdateListener(l3);
a1.addListener(listener);
+ a1.addListener(myListener);
mActivityRule.runOnUiThread(() -> {
a1.start();
});
// Wait for the anim to finish.
- verify(listener, within(200)).onAnimationEnd(a1);
+ verify(listener, within(200)).onAnimationEnd(a1, false);
+ // Also make sure the onAnimationEnd(anim) is called.
+ assertTrue(myListener.mEndIsCalled);
verify(l1, times(0)).onAnimationUpdate(a1);
verify(l2, times(0)).onAnimationUpdate(a1);
@@ -344,6 +352,7 @@
@Test
public void testValuesSetterAndGetter() throws Throwable {
+ final AnimatorSetTest.MyListener myListener = new AnimatorSetTest.MyListener();
ValueAnimator a2 = ValueAnimator.ofPropertyValuesHolder();
PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f);
PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("scaleY", 1f, 2f);
@@ -366,12 +375,15 @@
});
AnimatorListenerAdapter l1 = mock(AnimatorListenerAdapter.class);
a1.addListener(l1);
+ a1.addListener(myListener);
mActivityRule.runOnUiThread(() -> {
a1.start();
});
- verify(l1, within(200)).onAnimationEnd(a1);
+ verify(l1, within(200)).onAnimationEnd(a1, false);
+ // Also make sure the onAnimationEnd(anim) is called.
+ assertTrue(myListener.mEndIsCalled);
}
@Test
@@ -386,6 +398,7 @@
}
};
+ final AnimatorSetTest.MyListener myListener = new AnimatorSetTest.MyListener();
ValueAnimator a1 = new ValueAnimator();
a1.setDuration(50);
a1.setObjectValues(new PointF(0, 0), new PointF(1, 1));
@@ -400,11 +413,14 @@
});
AnimatorListenerAdapter l1 = mock(AnimatorListenerAdapter.class);
a1.addListener(l1);
+ a1.addListener(myListener);
mActivityRule.runOnUiThread(() -> {
a1.start();
});
- verify(l1, within(200)).onAnimationEnd(a1);
+ verify(l1, within(200)).onAnimationEnd(a1, false);
+ // Also make sure the onAnimationEnd(anim) is called.
+ assertTrue(myListener.mEndIsCalled);
}
@Test
@@ -572,7 +588,7 @@
final Animator.AnimatorListener watcher = mock(Animator.AnimatorListener.class);
animator.addListener(watcher);
mActivityRule.runOnUiThread(animator::start);
- verify(watcher, times(1)).onAnimationStart(animator);
+ verify(watcher, times(1)).onAnimationStart(animator, false);
assertTrue(((Float)animator.getAnimatedValue()) >= 0.5f);
assertTrue(animator.getAnimatedFraction() >= 0.5f);
mActivityRule.runOnUiThread(animator::cancel);
diff --git a/tests/tests/colormode/Android.mk b/tests/tests/colormode/Android.mk
new file mode 100644
index 0000000..d187069
--- /dev/null
+++ b/tests/tests/colormode/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsColorModeTestCases
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/colormode/AndroidManifest.xml b/tests/tests/colormode/AndroidManifest.xml
new file mode 100644
index 0000000..be2c457
--- /dev/null
+++ b/tests/tests/colormode/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.colormode.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.colormode.DefaultColorModeActivity" />
+ <activity android:name="android.colormode.WideColorModeActivity"
+ android:colorMode="wideColorGamut" />
+ <activity android:name="android.colormode.AttributeWideColorModeActivity" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.colormode.cts"
+ android:label="Tests for the Color Mode APIs." >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/colormode/AndroidTest.xml b/tests/tests/colormode/AndroidTest.xml
new file mode 100644
index 0000000..3f797aa
--- /dev/null
+++ b/tests/tests/colormode/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Color Mode test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsColorModeTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.colormode.cts" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/tests/tests/colormode/src/android/colormode/AttributeWideColorModeActivity.java b/tests/tests/colormode/src/android/colormode/AttributeWideColorModeActivity.java
new file mode 100644
index 0000000..e78c1fb
--- /dev/null
+++ b/tests/tests/colormode/src/android/colormode/AttributeWideColorModeActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.colormode;
+
+import android.app.Activity;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+
+public class AttributeWideColorModeActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT);
+ }
+}
diff --git a/tests/tests/colormode/src/android/colormode/DefaultColorModeActivity.java b/tests/tests/colormode/src/android/colormode/DefaultColorModeActivity.java
new file mode 100644
index 0000000..c0906ec
--- /dev/null
+++ b/tests/tests/colormode/src/android/colormode/DefaultColorModeActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.colormode;
+
+import android.app.Activity;
+
+public class DefaultColorModeActivity extends Activity {
+}
diff --git a/tests/tests/colormode/src/android/colormode/WideColorModeActivity.java b/tests/tests/colormode/src/android/colormode/WideColorModeActivity.java
new file mode 100644
index 0000000..ff297d8
--- /dev/null
+++ b/tests/tests/colormode/src/android/colormode/WideColorModeActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.colormode;
+
+import android.app.Activity;
+
+public class WideColorModeActivity extends Activity {
+}
diff --git a/tests/tests/colormode/src/android/colormode/cts/AttributeWideColorModeTest.java b/tests/tests/colormode/src/android/colormode/cts/AttributeWideColorModeTest.java
new file mode 100644
index 0000000..a129b79
--- /dev/null
+++ b/tests/tests/colormode/src/android/colormode/cts/AttributeWideColorModeTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.colormode.cts;
+
+import android.app.Activity;
+import android.colormode.AttributeWideColorModeActivity;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Window;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AttributeWideColorModeTest {
+ @Rule
+ public ActivityTestRule<AttributeWideColorModeActivity> mActivityRule =
+ new ActivityTestRule<>(AttributeWideColorModeActivity.class);
+ private Activity mActivity;
+
+ @Before
+ public void setup() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @Test
+ public void testDefaultColorMode() throws Exception {
+ PackageManager pm = mActivity.getPackageManager();
+
+ ActivityInfo info = pm.getActivityInfo(mActivity.getComponentName(), 0);
+ assertEquals(ActivityInfo.COLOR_MODE_DEFAULT, info.colorMode);
+
+ Window window = mActivity.getWindow();
+ assertEquals(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT,
+ window.getAttributes().getColorMode());
+ }
+}
diff --git a/tests/tests/colormode/src/android/colormode/cts/DefaultColorModeTest.java b/tests/tests/colormode/src/android/colormode/cts/DefaultColorModeTest.java
new file mode 100644
index 0000000..06273eb
--- /dev/null
+++ b/tests/tests/colormode/src/android/colormode/cts/DefaultColorModeTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.colormode.cts;
+
+import android.colormode.DefaultColorModeActivity;
+import android.app.Activity;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Window;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class DefaultColorModeTest {
+ @Rule
+ public ActivityTestRule<DefaultColorModeActivity> mActivityRule =
+ new ActivityTestRule<>(DefaultColorModeActivity.class);
+ private Activity mActivity;
+
+ @Before
+ public void setup() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @Test
+ public void testDefaultColorMode() throws Exception {
+ PackageManager pm = mActivity.getPackageManager();
+
+ ActivityInfo info = pm.getActivityInfo(mActivity.getComponentName(), 0);
+ assertEquals(ActivityInfo.COLOR_MODE_DEFAULT, info.colorMode);
+
+ Window window = mActivity.getWindow();
+ assertEquals(ActivityInfo.COLOR_MODE_DEFAULT, window.getAttributes().getColorMode());
+ }
+}
diff --git a/tests/tests/colormode/src/android/colormode/cts/WideColorModeTest.java b/tests/tests/colormode/src/android/colormode/cts/WideColorModeTest.java
new file mode 100644
index 0000000..64f37c6
--- /dev/null
+++ b/tests/tests/colormode/src/android/colormode/cts/WideColorModeTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.colormode.cts;
+
+import android.app.Activity;
+import android.colormode.WideColorModeActivity;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Window;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class WideColorModeTest {
+ @Rule
+ public ActivityTestRule<WideColorModeActivity> mActivityRule =
+ new ActivityTestRule<>(WideColorModeActivity.class);
+ private Activity mActivity;
+
+ @Before
+ public void setup() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @Test
+ public void testDefaultColorMode() throws Exception {
+ PackageManager pm = mActivity.getPackageManager();
+
+ ActivityInfo info = pm.getActivityInfo(mActivity.getComponentName(), 0);
+ assertEquals(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT, info.colorMode);
+
+ Window window = mActivity.getWindow();
+ assertEquals(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT,
+ window.getAttributes().getColorMode());
+ }
+}
diff --git a/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java b/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java
index 2f58702..9b441a0 100644
--- a/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java
+++ b/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java
@@ -64,6 +64,8 @@
"name TEXT, age INTEGER, address TEXT);");
mDatabase.execSQL(
"CREATE TABLE blob_test (_id INTEGER PRIMARY KEY, name TEXT, data BLOB)");
+ mDatabase.execSQL(
+ "CREATE TABLE boolean_test (_id INTEGER PRIMARY KEY, value BOOLEAN)");
}
@Override
@@ -264,6 +266,22 @@
assertEquals("Mike", (String) contentValues.get("name"));
assertEquals("20", (String) contentValues.get("age"));
assertEquals("LA", (String) contentValues.get("address"));
+
+ mDatabase.execSQL("INSERT INTO boolean_test (value)" +
+ " VALUES (0);");
+ mDatabase.execSQL("INSERT INTO boolean_test (value)" +
+ " VALUES (1);");
+ cursor = mDatabase.query("boolean_test", new String[] {"value"},
+ null, null, null, null, null);
+ assertNotNull(cursor);
+
+ contentValues = new ContentValues();
+ cursor.moveToNext();
+ DatabaseUtils.cursorRowToContentValues(cursor, contentValues);
+ assertFalse(contentValues.getAsBoolean("value"));
+ cursor.moveToNext();
+ DatabaseUtils.cursorRowToContentValues(cursor, contentValues);
+ assertTrue(contentValues.getAsBoolean("value"));
}
public void testCursorStringToContentValues() {
diff --git a/tests/tests/graphics/AndroidManifest.xml b/tests/tests/graphics/AndroidManifest.xml
index c6cf40f..19dcf3a 100644
--- a/tests/tests/graphics/AndroidManifest.xml
+++ b/tests/tests/graphics/AndroidManifest.xml
@@ -50,6 +50,8 @@
<activity android:name="android.graphics.drawable.cts.DrawableStubActivity"
android:theme="@style/WhiteBackgroundNoWindowAnimation"/>
+
+ <activity android:name="android.graphics.fonts.cts.MockActivity"/>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
index 8c16193..6edb064 100644
--- a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
@@ -147,6 +147,18 @@
Typeface.createFromAsset(null, null);
}
+ @Test(expected=NullPointerException.class)
+ public void testCreateFromAssetNullPath() {
+ // input abnormal params.
+ Typeface.createFromAsset(mContext.getAssets(), null);
+ }
+
+ @Test(expected=RuntimeException.class)
+ public void testCreateFromAssetInvalidPath() {
+ // input abnormal params.
+ Typeface.createFromAsset(mContext.getAssets(), "invalid path");
+ }
+
@Test
public void testCreateFromAsset() {
Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
@@ -166,12 +178,24 @@
assertNotNull(typeface);
}
+ @Test(expected=RuntimeException.class)
+ public void testCreateFromFileWithInvalidPath() throws IOException {
+ File file = new File("/invalid/path");
+ Typeface.createFromFile(file);
+ }
+
@Test(expected=NullPointerException.class)
public void testCreateFromFileByFileNameNull() throws IOException {
// input abnormal params.
Typeface.createFromFile((String) null);
}
+ @Test(expected=RuntimeException.class)
+ public void testCreateFromFileByInvalidFileName() throws IOException {
+ // input abnormal params.
+ Typeface.createFromFile("/invalid/path");
+ }
+
@Test
public void testCreateFromFileByFileName() throws IOException {
Typeface typeface = Typeface.createFromFile(obtainPath());
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/MaskableIconDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/MaskableIconDrawableTest.java
new file mode 100644
index 0000000..311e7f4
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/MaskableIconDrawableTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Path;
+import android.graphics.Path.Direction;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.MaskableIconDrawable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.PathParser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MaskableIconDrawableTest {
+
+ private static final double SAFEZONE_INSET = .1;
+ private static final double DELTA = .01f;
+ private Path mMask = new Path();
+ private Path mSafeZone = new Path();
+ private ColorDrawable mBlueDrawable;
+ private ColorDrawable mRedDrawable;
+ private MaskableIconDrawable mDrawable;
+
+ @Before
+ public void setup() {
+ mBlueDrawable = new ColorDrawable(Color.BLUE);
+ mRedDrawable = new ColorDrawable(Color.RED);
+ mDrawable = new MaskableIconDrawable( mBlueDrawable, mRedDrawable);
+ mMask = PathParser.createPathFromPathData(
+ Resources.getSystem().getString(com.android.internal.R.string.config_icon_mask));
+ int sInset = (int) (SAFEZONE_INSET * MaskableIconDrawable.MASK_SIZE);
+ mSafeZone.addCircle(MaskableIconDrawable.MASK_SIZE/2,MaskableIconDrawable.MASK_SIZE/2,
+ MaskableIconDrawable.MASK_SIZE/2/2 - sInset, Direction.CW);
+ }
+
+ @Test
+ public void testDeviceConfigMask_bounds() {
+ assertNotNull(mMask);
+
+ // Bounds should be [100 x 100]
+ RectF bounds = new RectF();
+ mMask.computeBounds(bounds, true);
+ System.out.println("MERONG:" + bounds.toShortString());
+ assertTrue("Mask top should be larger than or equal to 0", -DELTA <= bounds.top);
+ assertTrue("Mask left should be larger than or equal to 0", -DELTA <= bounds.left);
+ assertTrue("Mask bottom should be smaller than or equal to" +
+ MaskableIconDrawable.MASK_SIZE,
+ MaskableIconDrawable.MASK_SIZE + DELTA >= bounds.bottom);
+ assertTrue("Mask right should be smaller than or equal to " +
+ MaskableIconDrawable.MASK_SIZE,
+ MaskableIconDrawable.MASK_SIZE + DELTA >= bounds.right);
+ }
+
+ @Test
+ public void testDeviceConfigMask_largerThanSafezone() {
+ assertNotNull(mMask);
+
+ // MASK intersection SafeZone = SafeZone
+ Region maskRegion = new Region(0, 0, (int) MaskableIconDrawable.MASK_SIZE,
+ (int) MaskableIconDrawable.MASK_SIZE);
+ maskRegion.setPath(mMask, maskRegion);
+
+ Region safeZoneRegion = new Region(0, 0, (int) MaskableIconDrawable.MASK_SIZE,
+ (int) MaskableIconDrawable.MASK_SIZE);
+ safeZoneRegion.setPath(mSafeZone, safeZoneRegion);
+
+ Region intersectRegion = new Region();
+ boolean result = maskRegion.op(safeZoneRegion, intersectRegion, Region.Op.INTERSECT);
+
+ // resultRegion should be exactly same as the safezone
+ // Test if intersectRegion is smaller than safeZone, in which case test will fail.
+ Region subtractRegion = new Region();
+ result = safeZoneRegion.op(intersectRegion, subtractRegion, Op.DIFFERENCE);
+ assertFalse("Mask is not respecting the safezone.", result);
+ }
+
+ @Test
+ public void testDeviceConfigMask_isConvex() {
+ assertNotNull(mMask);
+
+ assertTrue("Mask is not convex", mMask.isConvex());
+ }
+
+ @Test
+ public void testGetLayers() {
+ assertEquals("Background layer is not the same.",
+ mBlueDrawable, mDrawable.getBackground());
+ assertEquals("Foreground layer is not the same.",
+ mRedDrawable, mDrawable.getForeground());
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java b/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java
new file mode 100644
index 0000000..6cf7e29
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.graphics.fonts.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.graphics.fonts.FontRequest;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link android.graphics.fonts.FontRequest}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FontRequestTest {
+ private static final String PROVIDER = "com.test.fontprovider";
+ private static final String QUERY = "my_family_name";
+
+ @Test
+ public void testWriteToParcel() {
+ // GIVEN a FontRequest
+ FontRequest request = new FontRequest(PROVIDER, QUERY);
+
+ // WHEN we write it to a Parcel
+ Parcel dest = Parcel.obtain();
+ request.writeToParcel(dest, 0);
+ dest.setDataPosition(0);
+
+ // THEN we create from that parcel and get the same values.
+ FontRequest result = FontRequest.CREATOR.createFromParcel(dest);
+ assertEquals(PROVIDER, result.getProviderAuthority());
+ assertEquals(QUERY, result.getQuery());
+ }
+
+ @Test
+ public void testConstructorWithNullAuthority() {
+ try {
+ // WHEN we create a request with a null authority
+ new FontRequest(null, QUERY);
+ } catch (NullPointerException e) {
+ // THEN we expect an exception to be raised.
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testConstructorWithNullQuery() {
+ try {
+ // WHEN we create a request with a null query
+ new FontRequest(PROVIDER, null);
+ } catch (NullPointerException e) {
+ // THEN we expect an exception to be raised.
+ return;
+ }
+ fail();
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java b/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java
new file mode 100644
index 0000000..06e2e3f
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.graphics.fonts.cts;
+
+import static android.content.res.AssetManager.ACCESS_BUFFER;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import android.graphics.fonts.FontResult;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Tests for {@link FontResult}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FontResultTest {
+ private static final ParcelFileDescriptor FILE_DESCRIPTOR =
+ new ParcelFileDescriptor(new FileDescriptor());
+ private static final int TTC_INDEX = 3;
+ private static final String FONT_VARIATION_SETTINGS = "my_settings";
+ private static final int STYLE = Typeface.BOLD_ITALIC;
+ private static final String TEST_FONT_FILE = "samplefont.ttf";
+ private Resources mResources;
+ private Activity mActivity;
+
+ @Rule
+ public ActivityTestRule<MockActivity> mActivityRule =
+ new ActivityTestRule<>(MockActivity.class);
+
+ @Before
+ public void setup() throws Throwable {
+ mActivity = mActivityRule.getActivity();
+ mResources = mActivity.getResources();
+ }
+
+ @Test
+ public void testWriteToParcel() throws IOException {
+ ParcelFileDescriptor pfd = loadFont();
+ // GIVEN a FontResult
+ FontResult fontResult = new FontResult(pfd, TTC_INDEX, FONT_VARIATION_SETTINGS, STYLE);
+
+ // WHEN we write it to a Parcel
+ Parcel dest = Parcel.obtain();
+ fontResult.writeToParcel(dest, 0);
+ dest.setDataPosition(0);
+
+ // THEN we create from that parcel and get the same values.
+ FontResult result = FontResult.CREATOR.createFromParcel(dest);
+ assertNotNull(result.getFileDescriptor());
+ assertEquals(TTC_INDEX, result.getTtcIndex());
+ assertEquals(FONT_VARIATION_SETTINGS, result.getFontVariationSettings());
+ assertEquals(STYLE, result.getStyle());
+ }
+
+ @Test
+ public void testConstructorWithNullFileDescriptor() {
+ try {
+ // WHEN we create a result with a null file descriptor
+ new FontResult(null, TTC_INDEX, FONT_VARIATION_SETTINGS, STYLE);
+ } catch (NullPointerException e) {
+ // THEN we expect an exception to be raised.
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testConstructorWithNullFontVariationSettings() {
+ // WHEN we create a result with a null fontVariationSettings
+ FontResult fontResult = new FontResult(FILE_DESCRIPTOR, TTC_INDEX, null, STYLE);
+
+ // THEN we expect no exception to be raised, and null to be stored as the value.
+ assertNull(fontResult.getFontVariationSettings());
+ }
+
+ private ParcelFileDescriptor loadFont() {
+ File cacheFile = null;
+ try {
+ cacheFile = new File(mActivity.getCacheDir(), TEST_FONT_FILE);
+ cacheFile.getParentFile().mkdirs();
+ copyToCacheFile("samplefont.ttf", cacheFile);
+ return ParcelFileDescriptor.open(cacheFile, MODE_READ_ONLY);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (cacheFile != null) {
+ cacheFile.delete();
+ }
+ }
+ return null;
+ }
+
+ private void copyToCacheFile(final String assetPath, final File cacheFile)
+ throws IOException {
+ try (InputStream is = mResources.getAssets().open(assetPath, ACCESS_BUFFER);
+ FileOutputStream fos = new FileOutputStream(cacheFile, false)) {
+ byte[] buffer = new byte[1024];
+ int readLen;
+ while ((readLen = is.read(buffer)) != -1) {
+ fos.write(buffer, 0, readLen);
+ }
+ }
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/cts/MockActivity.java b/tests/tests/graphics/src/android/graphics/fonts/cts/MockActivity.java
new file mode 100644
index 0000000..3fbf255
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/cts/MockActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts.cts;
+
+import android.app.Activity;
+
+public class MockActivity extends Activity {
+
+}
\ No newline at end of file
diff --git a/tests/tests/hardware/Android.mk b/tests/tests/hardware/Android.mk
index 6e14198..957c012 100644
--- a/tests/tests/hardware/Android.mk
+++ b/tests/tests/hardware/Android.mk
@@ -34,7 +34,8 @@
ctstestrunner \
mockito-target-minus-junit4 \
platform-test-annotations \
- ub-uiautomator
+ ub-uiautomator \
+ legacy-android-test
LOCAL_JNI_SHARED_LIBRARIES := libctshardware_jni libnativehelper_compat_libc++
diff --git a/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java b/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
index 6d1f0d1..0896d71 100644
--- a/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
@@ -61,6 +61,9 @@
buffer = HardwareBuffer.create(2, 4, HardwareBuffer.RGB_565, 1,
HardwareBuffer.USAGE0_CPU_READ);
assertEquals(HardwareBuffer.RGB_565, buffer.getFormat());
+ buffer = HardwareBuffer.create(2, 1, HardwareBuffer.BLOB, 1,
+ HardwareBuffer.USAGE0_CPU_READ);
+ assertEquals(HardwareBuffer.BLOB, buffer.getFormat());
}
@Test
@@ -87,6 +90,11 @@
HardwareBuffer.USAGE0_CPU_READ);
} catch (IllegalArgumentException e) {}
assertEquals(null, buffer);
+ try {
+ buffer = HardwareBuffer.create(2, 2, HardwareBuffer.BLOB, 1,
+ HardwareBuffer.USAGE0_CPU_READ);
+ } catch (IllegalArgumentException e) {}
+ assertEquals(null, buffer);
}
@Test
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 98f85e7..807330e 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -53,9 +53,11 @@
import com.google.common.collect.ImmutableSet;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.Context;
import android.os.Build;
import android.os.SystemProperties;
import android.security.KeyStoreException;
+import android.security.keystore.AttestationUtils;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.test.AndroidTestCase;
@@ -362,6 +364,12 @@
}
}
+ public void testDeviceIdAttestation() throws Exception {
+ testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_SERIAL);
+ testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_IMEI);
+ testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_MEID);
+ }
+
@SuppressWarnings("deprecation")
private void testRsaAttestation(byte[] challenge, boolean includeValidityDates, int keySize,
int purposes, String[] paddingModes) throws Exception {
@@ -865,4 +873,14 @@
}
}
}
+
+ private void testDeviceIdAttestationFailure(int idType) throws Exception {
+ try {
+ AttestationUtils.attestDeviceIds(getContext(), new int[] {idType}, "123".getBytes());
+ fail("Attestation should have failed.");
+ } catch (SecurityException e) {
+ // Attestation is expected to fail with a SecurityException as we do not hold
+ // READ_PRIVILEGED_PHONE_STATE permission.
+ }
+ }
}
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index 9064ade..5123f2c 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -27,6 +27,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER" />
+ <uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
index 987d7c7..c460519 100644
--- a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
@@ -24,6 +24,7 @@
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
+import android.os.ParcelFileDescriptor;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -31,6 +32,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.HashMap;
@@ -196,6 +198,46 @@
} finally {
muxer.release();
}
+
+ // Test FileDescriptor Constructor expect sucess.
+ RandomAccessFile file = null;
+ try {
+ file = new RandomAccessFile(outputFile, "rws");
+ muxer = new MediaMuxer(file.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
+ } finally {
+ file.close();
+ muxer.release();
+ }
+
+ // Test FileDescriptor Constructor expect exception with read only mode.
+ RandomAccessFile file2 = null;
+ try {
+ file2 = new RandomAccessFile(outputFile, "r");
+ muxer = new MediaMuxer(file2.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ fail("should throw IOException.");
+ } catch (IOException e) {
+ // expected
+ } finally {
+ file2.close();
+ // No need to release the muxer.
+ }
+
+ // Test FileDescriptor Constructor expect NO exception with write only mode.
+ ParcelFileDescriptor out = null;
+ try {
+ out = ParcelFileDescriptor.open(new File(outputFile),
+ ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE);
+ muxer = new MediaMuxer(out.getFileDescriptor(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ } catch (IllegalArgumentException e) {
+ fail("should not throw IllegalArgumentException.");
+ } catch (IOException e) {
+ fail("should not throw IOException.");
+ } finally {
+ out.close();
+ muxer.release();
+ }
+
new File(outputFile).delete();
}
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index 2801dd0..3fe76f8 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -680,30 +680,21 @@
}
}
- // Test passing a write-only FileDescriptor and expect IOException.
+ // Test passing a write-only FileDescriptor and expect NO IOException.
if (mFileIndex == 3) {
- ParcelFileDescriptor out = null;
- String path = null;
try {
- path = OUTPUT_PATH + '9';
+ ParcelFileDescriptor out = null;
+ String path = OUTPUT_PATH + mFileIndex;
out = ParcelFileDescriptor.open(new File(path),
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE);
mMediaRecorder.setNextOutputFile(out.getFileDescriptor());
- fail("Expect IOException.");
+ out.close();
+ recordFileList.add(path);
+ mFileIndex++;
} catch (IOException e) {
- // Expected.
- } finally {
- try {
- out.close();
- new File(path).delete();
- } catch (IOException e) {
- fail("Fail to close file");
- }
+ fail("Fail to set next output file error: " + e);
}
- }
-
- // only record 5 files.
- if (mFileIndex < 6) {
+ } else if (mFileIndex < 6) {
try {
String path = OUTPUT_PATH + mFileIndex;
mMediaRecorder.setNextOutputFile(path);
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index b10d9df..50b3033 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -15,15 +15,33 @@
*/
package android.media.cts;
+import com.android.compatibility.common.util.SystemUtil;
+
import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import android.test.InstrumentationTestCase;
import android.test.UiThreadTest;
+import android.view.KeyEvent;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
public class MediaSessionManagerTest extends InstrumentationTestCase {
+ private static final String TAG = "MediaSessionManagerTest";
+ private static final int TIMEOUT_MS = 3000;
+ private static final int WAIT_MS = 500;
+
private MediaSessionManager mSessionManager;
@Override
@@ -69,4 +87,166 @@
// TODO enable a notification listener, test again, disable, verify
// updates stopped
}
+
+ private void assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount) {
+ assertTrue(lhs.getKeyCode() == keyCode
+ && lhs.getAction() == action
+ && lhs.getRepeatCount() == repeatCount);
+ }
+
+ private void injectInputEvent(int keyCode, boolean longPress) throws IOException {
+ // Injecting key with instrumentation requires a window/view, but we don't have it.
+ // Inject key event through the adb commend to workaround.
+ final String command = "input keyevent " + (longPress ? "--longpress " : "") + keyCode;
+ SystemUtil.runShellCommand(getInstrumentation(), command);
+ }
+
+ public void testSetOnVolumeKeyLongPressListener() throws Exception {
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ Handler handler = new Handler(handlerThread.getLooper());
+
+ // Ensure that the listener is called for long-press.
+ VolumeKeyLongPressListener listener = new VolumeKeyLongPressListener(3, handler);
+ mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
+ injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
+ assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(listener.mKeyEvents.size(), 3);
+ assertKeyEventEquals(listener.mKeyEvents.get(0),
+ KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 0);
+ assertKeyEventEquals(listener.mKeyEvents.get(1),
+ KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 1);
+ assertKeyEventEquals(listener.mKeyEvents.get(2),
+ KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_UP, 0);
+
+ // Ensure the the listener isn't called for short-press.
+ listener = new VolumeKeyLongPressListener(1, handler);
+ mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
+ injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
+ assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(listener.mKeyEvents.size(), 0);
+
+ // Ensure that the listener isn't called anymore.
+ mSessionManager.setOnVolumeKeyLongPressListener(null, handler);
+ injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
+ assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(listener.mKeyEvents.size(), 0);
+ }
+
+ public void testSetOnMediaKeyListener() throws Exception {
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ Handler handler = new Handler(handlerThread.getLooper());
+
+ MediaSessionCallback callback = new MediaSessionCallback(2);
+ MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
+ session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+ | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ session.setCallback(callback, handler);
+ PlaybackState state = new PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
+ // Fake the media session service so this session can take the media key events.
+ session.setPlaybackState(state);
+ session.setActive(true);
+
+ // Ensure that the listener is called for media key event,
+ // and any other media sessions don't get the key.
+ MediaKeyListener listener = new MediaKeyListener(2, true, handler);
+ mSessionManager.setOnMediaKeyListener(listener, handler);
+ injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
+ assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(listener.mKeyEvents.size(), 2);
+ assertKeyEventEquals(listener.mKeyEvents.get(0),
+ KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
+ assertKeyEventEquals(listener.mKeyEvents.get(1),
+ KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
+ assertFalse(callback.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(callback.mKeyEvents.size(), 0);
+
+ // Ensure that the listener is called for media key event,
+ // and another media session gets the key.
+ listener = new MediaKeyListener(2, false, handler);
+ mSessionManager.setOnMediaKeyListener(listener, handler);
+ injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
+ assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(listener.mKeyEvents.size(), 2);
+ assertKeyEventEquals(listener.mKeyEvents.get(0),
+ KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
+ assertKeyEventEquals(listener.mKeyEvents.get(1),
+ KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
+ assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(callback.mKeyEvents.size(), 2);
+ assertKeyEventEquals(callback.mKeyEvents.get(0),
+ KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
+ assertKeyEventEquals(callback.mKeyEvents.get(1),
+ KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
+
+ // Ensure that the listener isn't called anymore.
+ listener = new MediaKeyListener(1, true, handler);
+ mSessionManager.setOnMediaKeyListener(listener, handler);
+ mSessionManager.setOnMediaKeyListener(null, handler);
+ injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
+ assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(listener.mKeyEvents.size(), 0);
+ }
+
+ private class VolumeKeyLongPressListener
+ implements MediaSessionManager.OnVolumeKeyLongPressListener {
+ private final List<KeyEvent> mKeyEvents = new ArrayList<>();
+ private final CountDownLatch mCountDownLatch;
+ private final Handler mHandler;
+
+ public VolumeKeyLongPressListener(int count, Handler handler) {
+ mCountDownLatch = new CountDownLatch(count);
+ mHandler = handler;
+ }
+
+ @Override
+ public void onVolumeKeyLongPress(KeyEvent event) {
+ mKeyEvents.add(event);
+ // Ensure the listener is called on the thread.
+ assertEquals(mHandler.getLooper(), Looper.myLooper());
+ mCountDownLatch.countDown();
+ }
+ }
+
+ private class MediaKeyListener implements MediaSessionManager.OnMediaKeyListener {
+ private final CountDownLatch mCountDownLatch;
+ private final boolean mConsume;
+ private final Handler mHandler;
+ private final List<KeyEvent> mKeyEvents = new ArrayList<>();
+
+ public MediaKeyListener(int count, boolean consume, Handler handler) {
+ mCountDownLatch = new CountDownLatch(count);
+ mConsume = consume;
+ mHandler = handler;
+ }
+
+ @Override
+ public boolean onMediaKey(KeyEvent event) {
+ mKeyEvents.add(event);
+ // Ensure the listener is called on the thread.
+ assertEquals(mHandler.getLooper(), Looper.myLooper());
+ mCountDownLatch.countDown();
+ return mConsume;
+ }
+ }
+
+ private class MediaSessionCallback extends MediaSession.Callback {
+ private final CountDownLatch mCountDownLatch;
+ private final List<KeyEvent> mKeyEvents = new ArrayList<>();
+
+ private MediaSessionCallback(int count) {
+ mCountDownLatch = new CountDownLatch(count);
+ }
+
+ public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+ KeyEvent event = (KeyEvent) mediaButtonIntent.getParcelableExtra(
+ Intent.EXTRA_KEY_EVENT);
+ assertNotNull(event);
+ mKeyEvents.add(event);
+ mCountDownLatch.countDown();
+ return true;
+ }
+ }
}
diff --git a/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java b/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
index 13ff24b..049eeab 100644
--- a/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
+++ b/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
@@ -26,17 +26,18 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.StubTestInvocationListener;
import com.android.tradefed.targetprep.BuildError;
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.testtype.AndroidJUnitTest;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.ZipUtil;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.InputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;
@@ -44,8 +45,6 @@
import java.util.regex.Pattern;
import java.util.zip.ZipFile;
-import org.xmlpull.v1.XmlPullParserException;
-
/**
* Ensures that the appropriate media files exist on the device
*/
@@ -178,11 +177,13 @@
* TargetSetupError is thrown. Otherwise, the mLocalMediaPath variable is set to the path of
* this subdirectory.
*/
- private void updateLocalMediaPath(File mediaFolder) throws TargetSetupError {
+ private void updateLocalMediaPath(ITestDevice device, File mediaFolder)
+ throws TargetSetupError {
String[] subDirs = mediaFolder.list();
if (subDirs.length != 1) {
throw new TargetSetupError(String.format(
- "Unexpected contents in directory %s", mediaFolder.getAbsolutePath()));
+ "Unexpected contents in directory %s", mediaFolder.getAbsolutePath()),
+ device.getDeviceDescriptor());
}
mLocalMediaPath = new File(mediaFolder, subDirs[0]).getAbsolutePath();
}
@@ -192,16 +193,19 @@
* Updates mLocalMediaPath to be the pathname of the directory containing bbb_short and
* bbb_full media directories.
*/
- private void downloadMediaToHost(File mediaFolder) throws TargetSetupError {
+ private void downloadMediaToHost(ITestDevice device, IBuildInfo buildInfo, File mediaFolder)
+ throws TargetSetupError {
URL url;
try {
// Get download URL from dynamic configuration service
- DynamicConfigHostSide config = new DynamicConfigHostSide(DYNAMIC_CONFIG_MODULE);
- String mediaUrlString = config.getValue(MEDIA_FILES_URL_KEY);
+ File config =
+ DynamicConfigHostSide.getDynamicConfigFile(buildInfo, DYNAMIC_CONFIG_MODULE);
+ String mediaUrlString =
+ DynamicConfigHostSide.getValueFromConfig(config, MEDIA_FILES_URL_KEY);
url = new URL(mediaUrlString);
} catch (IOException | XmlPullParserException e) {
throw new TargetSetupError("Trouble finding media file download location with " +
- "dynamic configuration", e);
+ "dynamic configuration", e, device.getDeviceDescriptor());
}
File mediaFolderZip = new File(mediaFolder.getAbsolutePath() + ".zip");
try {
@@ -215,7 +219,8 @@
} catch (IOException e) {
FileUtil.recursiveDelete(mediaFolder);
throw new TargetSetupError("Failed to download and open media files on host, the"
- + " device requires these media files for CTS media tests", e);
+ + " device requires these media files for CTS media tests", e,
+ device.getDeviceDescriptor());
} finally {
FileUtil.deleteFile(mediaFolderZip);
}
@@ -288,10 +293,10 @@
// runs of MediaPreparer. Assume media files exist inside.
// Else, create directory if needed and download/extract media files inside.
mediaFolder.mkdirs();
- downloadMediaToHost(mediaFolder);
+ downloadMediaToHost(device, buildInfo, mediaFolder);
}
// set mLocalMediaPath to where the CTS media files have been extracted
- updateLocalMediaPath(mediaFolder);
+ updateLocalMediaPath(device, mediaFolder);
}
logInfo("Media files located on host at: %s", mLocalMediaPath);
copyMediaFiles(device);
@@ -336,7 +341,7 @@
}
/* Special listener for setting MediaPreparer instance variable values */
- private class MediaPreparerListener extends StubTestInvocationListener {
+ private class MediaPreparerListener implements ITestInvocationListener {
@Override
public void testEnded(TestIdentifier test, Map<String, String> metrics) {
diff --git a/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java b/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
index 04c3900..a175112 100644
--- a/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
+++ b/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
@@ -21,7 +21,6 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.targetprep.TargetSetupError;
import junit.framework.TestCase;
@@ -39,7 +38,7 @@
super.setUp();
mMediaPreparer = new MediaPreparer();
mMockDevice = EasyMock.createMock(ITestDevice.class);
- mMockBuildInfo = new BuildInfo("0", "", "");
+ mMockBuildInfo = new BuildInfo("0", "");
mOptionSetter = new OptionSetter(mMediaPreparer);
}
diff --git a/tests/tests/nativehardware/Android.mk b/tests/tests/nativehardware/Android.mk
index c19511e..3abaccb 100644
--- a/tests/tests/nativehardware/Android.mk
+++ b/tests/tests/nativehardware/Android.mk
@@ -32,6 +32,7 @@
LOCAL_SHARED_LIBRARIES := \
libandroid \
+ libandroid_runtime \
libutils \
liblog \
diff --git a/tests/tests/nativehardware/src/AHardwareBufferTest.cpp b/tests/tests/nativehardware/src/AHardwareBufferTest.cpp
index 37a51e4..f851d87 100644
--- a/tests/tests/nativehardware/src/AHardwareBufferTest.cpp
+++ b/tests/tests/nativehardware/src/AHardwareBufferTest.cpp
@@ -23,6 +23,7 @@
#include <sys/un.h>
#include <android/hardware_buffer.h>
+#include <android_runtime/android_hardware_HardwareBuffer.h>
#include <ui/GraphicBuffer.h>
#include <utils/Errors.h>
@@ -42,55 +43,6 @@
return reinterpret_cast<AHardwareBuffer*>(buffer);
}
-// This function is temporarily necessary to convert between bit versions.
-static inline uint32_t convertGralloc1ToGralloc0UsageBits(uint64_t usage0,
- uint64_t usage1) {
- uint32_t bits = 0;
- if (usage0 & AHARDWAREBUFFER_USAGE0_CPU_READ)
- bits |= GRALLOC_USAGE_SW_READ_RARELY;
- if (usage0 & AHARDWAREBUFFER_USAGE0_CPU_READ_OFTEN)
- bits |= GRALLOC_USAGE_SW_READ_OFTEN;
- if (usage0 & AHARDWAREBUFFER_USAGE0_CPU_WRITE)
- bits |= GRALLOC_USAGE_SW_WRITE_RARELY;
- if (usage0 & AHARDWAREBUFFER_USAGE0_CPU_WRITE_OFTEN)
- bits |= GRALLOC_USAGE_SW_WRITE_OFTEN;
- if (usage0 & AHARDWAREBUFFER_USAGE0_GPU_SAMPLED_IMAGE)
- bits |= GRALLOC_USAGE_HW_TEXTURE;
- if (usage0 & AHARDWAREBUFFER_USAGE0_GPU_COLOR_OUTPUT)
- bits |= GRALLOC_USAGE_HW_RENDER;
- // Not sure what this should be.
- if (usage0 & AHARDWAREBUFFER_USAGE0_GPU_STORAGE_IMAGE) bits |= 0;
- // Not sure what this should be.
- if (usage0 & AHARDWAREBUFFER_USAGE0_GPU_CUBEMAP) bits |= 0;
- // Not yet supported in gralloc1.
- // if (usage0 & AHARDWAREBUFFER_USAGE0_GPU_DATA_BUFFER) bits |= 0;
- if (usage0 & AHARDWAREBUFFER_USAGE0_VIDEO_ENCODE)
- bits |= GRALLOC_USAGE_HW_VIDEO_ENCODER;
- if (usage0 & AHARDWAREBUFFER_USAGE0_PROTECTED_CONTENT)
- bits |= GRALLOC_USAGE_PROTECTED;
-
- (void)usage1;
-
- return bits;
-}
-
-static uint32_t convertFromGraphicBufferFormat(uint32_t format) {
- switch (format) {
- case PIXEL_FORMAT_RGBA_8888:
- return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
- case PIXEL_FORMAT_RGBA_FP16:
- return AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT;
- case PIXEL_FORMAT_RGBX_8888:
- return AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM;
- case PIXEL_FORMAT_RGB_565:
- return AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM;
- case PIXEL_FORMAT_RGB_888:
- return AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
- default:
- return 0;
- }
-}
-
static ::testing::AssertionResult BuildFailureMessage(uint32_t expected,
uint32_t actual, const char* type) {
return ::testing::AssertionFailure() << "Buffer " << type << " do not match"
@@ -110,11 +62,14 @@
return BuildFailureMessage(desc.layers,
static_cast<uint32_t>(buffer->layerCount), "layers");
if (static_cast<uint32_t>(buffer->usage) !=
- convertGralloc1ToGralloc0UsageBits(desc.usage0, desc.usage1))
+ android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+ desc.usage0, desc.usage1))
return BuildFailureMessage(
- convertGralloc1ToGralloc0UsageBits(desc.usage0, desc.usage1),
+ android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+ desc.usage0, desc.usage1),
static_cast<uint32_t>(buffer->usage), "usages");
- if (convertFromGraphicBufferFormat(buffer->getPixelFormat()) != desc.format)
+ if (android_hardware_HardwareBuffer_convertFromPixelFormat(
+ buffer->getPixelFormat()) != desc.format)
return BuildFailureMessage(desc.format,
static_cast<uint32_t>(buffer->format), "formats");
return ::testing::AssertionSuccess();
@@ -135,6 +90,28 @@
EXPECT_EQ(BAD_VALUE, res);
}
+// Test that passing in NULL values to allocate works as expected.
+TEST(AHardwareBufferTest, AHardwareBuffer_allocate_BlobFormatRequiresHeight1) {
+ AHardwareBuffer* buffer;
+ AHardwareBuffer_Desc desc;
+
+ desc.width = 2;
+ desc.height = 4;
+ desc.layers = 1;
+ desc.usage0 = AHARDWAREBUFFER_USAGE0_CPU_READ;
+ desc.usage1 = 0;
+ desc.format = AHARDWAREBUFFER_FORMAT_BLOB;
+ int res = AHardwareBuffer_allocate(&desc, &buffer);
+ EXPECT_EQ(BAD_VALUE, res);
+
+ desc.height = 1;
+ res = AHardwareBuffer_allocate(&desc, &buffer);
+ EXPECT_EQ(NO_ERROR, res);
+ EXPECT_TRUE(CheckAHardwareBufferMatchesDesc(buffer, desc));
+ AHardwareBuffer_release(buffer);
+ buffer = NULL;
+}
+
// Test that allocate can create an AHardwareBuffer correctly.
TEST(AHardwareBufferTest, AHardwareBuffer_allocate_Succeeds) {
AHardwareBuffer* buffer = NULL;
diff --git a/tests/tests/nativemedia/aaudio/Android.mk b/tests/tests/nativemedia/aaudio/Android.mk
new file mode 100644
index 0000000..a075f9d
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/Android.mk
@@ -0,0 +1,47 @@
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Build the unit tests.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := CtsNativeMediaAAudioTestCases
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+
+LOCAL_C_INCLUDES := \
+ frameworks/av/media/liboboe/include
+
+LOCAL_SRC_FILES := \
+ src/test_aaudio.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ liboboe \
+ libutils
+
+LOCAL_STATIC_LIBRARIES := \
+ libgtest
+
+LOCAL_CTS_TEST_PACKAGE := android.nativemedia.aaudio
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_CFLAGS := -Werror -Wall
+
+include $(BUILD_CTS_EXECUTABLE)
diff --git a/tests/tests/nativemedia/aaudio/AndroidTest.xml b/tests/tests/nativemedia/aaudio/AndroidTest.xml
new file mode 100644
index 0000000..2224a27
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Native Media AAudio test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsNativeMediaAAudioTestCases->/data/local/tmp/CtsNativeMediaAAudioTestCases" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="CtsNativeMediaAAudioTestCases" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
new file mode 100644
index 0000000..f6cdadb
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
@@ -0,0 +1,373 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "AAudioTest"
+
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <oboe/OboeAudio.h>
+#include <oboe/OboeDefinitions.h>
+
+// Note that this "Oboe" audio API is in the process of being renamed "AAudio".
+// You may see both names until the conversion is complete.
+// TODO Remove this comment when Oboe has become AAudio.
+
+#define DEFAULT_STATE_TIMEOUT (500 * OBOE_NANOS_PER_MILLISECOND)
+
+// Test OboeStreamBuilder
+TEST(test_aaudio, oboe_stream_builder) {
+ const oboe_sample_rate_t requestedSampleRate1 = 48000;
+ const oboe_sample_rate_t requestedSampleRate2 = 44100;
+ const int32_t requestedSamplesPerFrame = 2;
+ const oboe_audio_format_t requestedDataFormat = OBOE_AUDIO_FORMAT_PCM16;
+
+ oboe_sample_rate_t sampleRate = -1;
+ int32_t samplesPerFrame = -1;
+ oboe_audio_format_t actualDataFormat;
+ OboeStreamBuilder oboeBuilder1;
+ OboeStreamBuilder oboeBuilder2;
+
+ // Use an OboeStreamBuilder to define the stream.
+ oboe_result_t result = Oboe_createStreamBuilder(&oboeBuilder1);
+ ASSERT_EQ(OBOE_OK, result);
+
+ // Request stream properties.
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSampleRate(oboeBuilder1, requestedSampleRate1));
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSamplesPerFrame(oboeBuilder1,
+ requestedSamplesPerFrame));
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setFormat(oboeBuilder1, requestedDataFormat));
+
+ // Check to make sure builder saved the properties.
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder1, &sampleRate));
+ EXPECT_EQ(requestedSampleRate1, sampleRate);
+
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSamplesPerFrame(oboeBuilder1, &samplesPerFrame));
+ EXPECT_EQ(requestedSamplesPerFrame, samplesPerFrame);
+
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getFormat(oboeBuilder1, &actualDataFormat));
+ EXPECT_EQ(requestedDataFormat, actualDataFormat);
+
+ result = OboeStreamBuilder_getSampleRate(0x0BADCAFE, &sampleRate); // ridiculous token
+ EXPECT_EQ(OBOE_ERROR_INVALID_HANDLE, result);
+
+ // Create a second builder and make sure they do not collide.
+ ASSERT_EQ(OBOE_OK, Oboe_createStreamBuilder(&oboeBuilder2));
+ ASSERT_NE(oboeBuilder1, oboeBuilder2);
+
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSampleRate(oboeBuilder2, requestedSampleRate2));
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder1, &sampleRate));
+ EXPECT_EQ(requestedSampleRate1, sampleRate);
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder2, &sampleRate));
+ EXPECT_EQ(requestedSampleRate2, sampleRate);
+
+ // Delete the builder.
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder1));
+
+ // Now it should no longer be valid.
+ // Note that test assumes we are using the HandleTracker. If we use plain pointers
+ // then it will be difficult to detect this kind of error.
+ result = OboeStreamBuilder_getSampleRate(oboeBuilder1, &sampleRate); // stale token
+ EXPECT_EQ(OBOE_ERROR_INVALID_HANDLE, result);
+
+ // Second builder should still be valid.
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder2, &sampleRate));
+ EXPECT_EQ(requestedSampleRate2, sampleRate);
+
+ // Delete the second builder.
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder2));
+
+ // Now it should no longer be valid. Assumes HandlerTracker used.
+ EXPECT_EQ(OBOE_ERROR_INVALID_HANDLE, OboeStreamBuilder_getSampleRate(oboeBuilder2,
+ &sampleRate));
+}
+
+// Test creating a default stream with everything unspecified.
+TEST(test_aaudio, oboe_stream_unspecified) {
+ OboeStreamBuilder oboeBuilder;
+ OboeStream oboeStream;
+ oboe_result_t result = OBOE_OK;
+
+ // Use an OboeStreamBuilder to define the stream.
+ result = Oboe_createStreamBuilder(&oboeBuilder);
+ ASSERT_EQ(OBOE_OK, result);
+
+ // Create an OboeStream using the Builder.
+ ASSERT_EQ(OBOE_OK, OboeStreamBuilder_openStream(oboeBuilder, &oboeStream));
+
+ // Cleanup
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder));
+ EXPECT_EQ(OBOE_OK, OboeStream_close(oboeStream));
+ // Can only close once. Second time should cause an error.
+ EXPECT_NE(OBOE_OK, OboeStream_close(oboeStream));
+}
+
+// Test Writing to an OboeStream
+void runtest_oboe_stream(oboe_sharing_mode_t requestedSharingMode) {
+ const oboe_sample_rate_t requestedSampleRate = 48000;
+ const oboe_sample_rate_t requestedSamplesPerFrame = 2;
+ const oboe_audio_format_t requestedDataFormat = OBOE_AUDIO_FORMAT_PCM16;
+
+ oboe_sample_rate_t actualSampleRate = -1;
+ int32_t actualSamplesPerFrame = -1;
+ oboe_audio_format_t actualDataFormat = OBOE_AUDIO_FORMAT_INVALID;
+ oboe_sharing_mode_t actualSharingMode;
+ oboe_size_frames_t framesPerBurst = -1;
+ int writeLoops = 0;
+
+ oboe_size_frames_t framesWritten = 0;
+ oboe_position_frames_t framesTotal = 0;
+ oboe_position_frames_t oboeFramesRead = 0;
+ oboe_position_frames_t oboeFramesRead1 = 0;
+ oboe_position_frames_t oboeFramesRead2 = 0;
+ oboe_position_frames_t oboeFramesWritten = 0;
+
+ oboe_nanoseconds_t timeoutNanos;
+
+ oboe_stream_state_t state = OBOE_STREAM_STATE_UNINITIALIZED;
+ OboeStreamBuilder oboeBuilder;
+ OboeStream oboeStream;
+
+ oboe_result_t result = OBOE_OK;
+
+ // Use an OboeStreamBuilder to define the stream.
+ result = Oboe_createStreamBuilder(&oboeBuilder);
+ ASSERT_EQ(OBOE_OK, result);
+
+ // Request stream properties.
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSampleRate(oboeBuilder, requestedSampleRate));
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSamplesPerFrame(oboeBuilder, requestedSamplesPerFrame));
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setFormat(oboeBuilder, requestedDataFormat));
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSharingMode(oboeBuilder, requestedSharingMode));
+
+ // Create an OboeStream using the Builder.
+ ASSERT_EQ(OBOE_OK, OboeStreamBuilder_openStream(oboeBuilder, &oboeStream));
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder));
+
+ EXPECT_EQ(OBOE_OK, OboeStream_getState(oboeStream, &state));
+ EXPECT_EQ(OBOE_STREAM_STATE_OPEN, state);
+
+ // Check to see what kind of stream we actually got.
+ EXPECT_EQ(OBOE_OK, OboeStream_getSampleRate(oboeStream, &actualSampleRate));
+ ASSERT_TRUE(actualSampleRate >= 44100 && actualSampleRate <= 96000); // TODO what is range?
+
+ EXPECT_EQ(OBOE_OK, OboeStream_getSamplesPerFrame(oboeStream, &actualSamplesPerFrame));
+ ASSERT_TRUE(actualSamplesPerFrame >= 1 && actualSamplesPerFrame <= 16); // TODO what is max?
+
+ EXPECT_EQ(OBOE_OK, OboeStream_getSharingMode(oboeStream, &actualSharingMode));
+ ASSERT_TRUE(actualSharingMode == OBOE_SHARING_MODE_EXCLUSIVE
+ || actualSharingMode == OBOE_SHARING_MODE_LEGACY);
+
+ EXPECT_EQ(OBOE_OK, OboeStream_getFormat(oboeStream, &actualDataFormat));
+ EXPECT_NE(OBOE_AUDIO_FORMAT_INVALID, actualDataFormat);
+
+ EXPECT_EQ(OBOE_OK, OboeStream_getFramesPerBurst(oboeStream, &framesPerBurst));
+ ASSERT_TRUE(framesPerBurst >= 16 && framesPerBurst <= 1024); // TODO what is min/max?
+
+ // Allocate a buffer for the audio data.
+ // TODO handle possibility of other data formats
+ ASSERT_TRUE(actualDataFormat == OBOE_AUDIO_FORMAT_PCM16);
+ size_t dataSizeSamples = framesPerBurst * actualSamplesPerFrame;
+ int16_t *data = (int16_t *) calloc(dataSizeSamples, sizeof(int16_t));
+ ASSERT_TRUE(nullptr != data);
+
+ // Prime the buffer.
+ timeoutNanos = 0;
+ do {
+ framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+ // There should be some room for priming the buffer.
+ framesTotal += framesWritten;
+ ASSERT_GE(framesWritten, 0);
+ ASSERT_LE(framesWritten, framesPerBurst);
+ } while (framesWritten > 0);
+ ASSERT_TRUE(framesTotal > 0);
+
+ // Start/write/pause more than once to see if it fails after the first time.
+ // Write some data and measure the rate to see if the timing is OK.
+ for (int numLoops = 0; numLoops < 2; numLoops++) {
+ // Start and wait for server to respond.
+ ASSERT_EQ(OBOE_OK, OboeStream_requestStart(oboeStream));
+ ASSERT_EQ(OBOE_OK, OboeStream_waitForStateChange(oboeStream,
+ OBOE_STREAM_STATE_STARTING,
+ &state,
+ DEFAULT_STATE_TIMEOUT));
+ EXPECT_EQ(OBOE_STREAM_STATE_STARTED, state);
+
+ // Write some data while we are running. Read counter should be advancing.
+ writeLoops = 1 * actualSampleRate / framesPerBurst; // 1 second
+ ASSERT_LT(2, writeLoops); // detect absurdly high framesPerBurst
+ timeoutNanos = 10 * OBOE_NANOS_PER_SECOND * framesPerBurst / actualSampleRate; // bursts
+ framesWritten = 1;
+ ASSERT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
+ oboeFramesRead1 = oboeFramesRead;
+ oboe_nanoseconds_t beginTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
+ do {
+ framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+ ASSERT_GE(framesWritten, 0);
+ ASSERT_LE(framesWritten, framesPerBurst);
+
+ framesTotal += framesWritten;
+ EXPECT_EQ(OBOE_OK, OboeStream_getFramesWritten(oboeStream, &oboeFramesWritten));
+ EXPECT_EQ(framesTotal, oboeFramesWritten);
+
+ // Try to get a more accurate measure of the sample rate.
+ if (beginTime == 0) {
+ EXPECT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
+ if (oboeFramesRead > oboeFramesRead1) { // is read pointer advancing
+ beginTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
+ oboeFramesRead1 = oboeFramesRead;
+ }
+ }
+ } while (framesWritten > 0 && writeLoops-- > 0);
+
+ EXPECT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead2));
+ oboe_nanoseconds_t endTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
+ ASSERT_GT(oboeFramesRead2, 0);
+ ASSERT_GT(oboeFramesRead2, oboeFramesRead1);
+ ASSERT_LE(oboeFramesRead2, oboeFramesWritten);
+
+ // TODO why is legacy so inaccurate?
+ const double rateTolerance = 200.0; // arbitrary tolerance for sample rate
+ if (requestedSharingMode != OBOE_SHARING_MODE_LEGACY) {
+ // Calculate approximate sample rate and compare with stream rate.
+ double seconds = (endTime - beginTime) / (double) OBOE_NANOS_PER_SECOND;
+ double measuredRate = (oboeFramesRead2 - oboeFramesRead1) / seconds;
+ ASSERT_NEAR(actualSampleRate, measuredRate, rateTolerance);
+ }
+
+ // Request async pause and wait for server to say that it has completed the pause.
+ ASSERT_EQ(OBOE_OK, OboeStream_requestPause(oboeStream));
+ EXPECT_EQ(OBOE_OK, OboeStream_waitForStateChange(oboeStream,
+ OBOE_STREAM_STATE_PAUSING,
+ &state,
+ DEFAULT_STATE_TIMEOUT));
+ EXPECT_EQ(OBOE_STREAM_STATE_PAUSED, state);
+ }
+
+ // Make sure the read counter is not advancing when we are paused.
+ ASSERT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
+ ASSERT_GE(oboeFramesRead, oboeFramesRead2); // monotonic increase
+
+ // Use this to sleep by waiting for something that won't happen.
+ OboeStream_waitForStateChange(oboeStream, OBOE_STREAM_STATE_PAUSED, &state, timeoutNanos);
+ ASSERT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead2));
+ EXPECT_EQ(oboeFramesRead, oboeFramesRead2);
+
+ // ------------------- TEST FLUSH -----------------
+ // Prime the buffer.
+ timeoutNanos = 0;
+ writeLoops = 100;
+ do {
+ framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+ framesTotal += framesWritten;
+ } while (framesWritten > 0 && writeLoops-- > 0);
+ EXPECT_EQ(0, framesWritten);
+
+ // Flush and wait for server to respond.
+ ASSERT_EQ(OBOE_OK, OboeStream_requestFlush(oboeStream));
+ EXPECT_EQ(OBOE_OK, OboeStream_waitForStateChange(oboeStream,
+ OBOE_STREAM_STATE_FLUSHING,
+ &state,
+ DEFAULT_STATE_TIMEOUT));
+ EXPECT_EQ(OBOE_STREAM_STATE_FLUSHED, state);
+
+ // After a flush, the read counter should be caught up with the write counter.
+ EXPECT_EQ(OBOE_OK, OboeStream_getFramesWritten(oboeStream, &oboeFramesWritten));
+ EXPECT_EQ(framesTotal, oboeFramesWritten);
+ EXPECT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
+ EXPECT_EQ(oboeFramesRead, oboeFramesWritten);
+
+ // The buffer should be empty after a flush so we should be able to write.
+ framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+ // There should be some room for priming the buffer.
+ ASSERT_TRUE(framesWritten > 0 && framesWritten <= framesPerBurst);
+
+ EXPECT_EQ(OBOE_OK, OboeStream_close(oboeStream));
+ free(data);
+}
+
+// Test Writing to an OboeStream using LEGACY sharing mode.
+TEST(test_aaudio, oboe_stream_legacy) {
+ runtest_oboe_stream(OBOE_SHARING_MODE_LEGACY);
+}
+
+/* TODO Enable exclusive mode test.
+// Test Writing to an OboeStream using EXCLUSIVE sharing mode.
+TEST(test_aaudio, oboe_stream_exclusive) {
+ runtest_oboe_stream(OBOE_SHARING_MODE_EXCLUSIVE);
+}
+*/
+
+#define OBOE_THREAD_ANSWER 1826375
+#define OBOE_THREAD_DURATION_MSEC 500
+
+static void *TestOboeStreamThreadProc(void *arg) {
+ OboeStream oboeStream = (OboeStream) reinterpret_cast<size_t>(arg);
+ oboe_stream_state_t state;
+
+ // Use this to sleep by waiting for something that won't happen.
+ EXPECT_EQ(OBOE_OK, OboeStream_getState(oboeStream, &state));
+ OboeStream_waitForStateChange(oboeStream, OBOE_STREAM_STATE_PAUSED, &state,
+ OBOE_THREAD_DURATION_MSEC * OBOE_NANOS_PER_MILLISECOND);
+ return reinterpret_cast<void *>(OBOE_THREAD_ANSWER);
+}
+
+// Test creating a stream related thread.
+TEST(test_aaudio, oboe_stream_thread_basic) {
+ OboeStreamBuilder oboeBuilder;
+ OboeStream oboeStream;
+ oboe_result_t result = OBOE_OK;
+ void *threadResult;
+
+ // Use an OboeStreamBuilder to define the stream.
+ result = Oboe_createStreamBuilder(&oboeBuilder);
+ ASSERT_EQ(OBOE_OK, result);
+
+ // Create an OboeStream using the Builder.
+ ASSERT_EQ(OBOE_OK, OboeStreamBuilder_openStream(oboeBuilder, &oboeStream));
+
+ // Start a thread.
+ ASSERT_EQ(OBOE_OK, OboeStream_createThread(oboeStream,
+ 10 * OBOE_NANOS_PER_MILLISECOND,
+ TestOboeStreamThreadProc,
+ reinterpret_cast<void *>(oboeStream)));
+ // Thread already started.
+ ASSERT_NE(OBOE_OK, OboeStream_createThread(oboeStream, // should fail!
+ 10 * OBOE_NANOS_PER_MILLISECOND,
+ TestOboeStreamThreadProc,
+ reinterpret_cast<void *>(oboeStream)));
+
+ // Wait for the thread to finish.
+ ASSERT_EQ(OBOE_OK, OboeStream_joinThread(oboeStream,
+ &threadResult, 2 * OBOE_THREAD_DURATION_MSEC * OBOE_NANOS_PER_MILLISECOND));
+ // The thread returns a special answer.
+ ASSERT_EQ(OBOE_THREAD_ANSWER, (int)reinterpret_cast<size_t>(threadResult));
+
+ // Thread should already be joined.
+ ASSERT_NE(OBOE_OK, OboeStream_joinThread(oboeStream, // should fail!
+ &threadResult, 2 * OBOE_THREAD_DURATION_MSEC * OBOE_NANOS_PER_MILLISECOND));
+
+ // Cleanup
+ EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder));
+ EXPECT_EQ(OBOE_OK, OboeStream_close(oboeStream));
+}
+
+int main(int argc, char **argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/tests/tests/preference2/AndroidManifest.xml b/tests/tests/preference2/AndroidManifest.xml
index 8d2b823..2c90fef 100644
--- a/tests/tests/preference2/AndroidManifest.xml
+++ b/tests/tests/preference2/AndroidManifest.xml
@@ -30,6 +30,7 @@
android:name="android.preference2.cts.PreferenceFromCodeActivity" >
</activity>
<activity android:name="android.preference2.cts.PreferencesFromXml" />
+ <activity android:name="android.preference2.cts.PreferencesFromXmlNested" />
<activity android:name="android.preference2.cts.PreferenceWithHeaders" />
<activity android:name="android.preference2.cts.FragmentPreferences" />
</application>
diff --git a/tests/tests/preference2/res/xml/pref_nested.xml b/tests/tests/preference2/res/xml/pref_nested.xml
new file mode 100755
index 0000000..0c03b2d
--- /dev/null
+++ b/tests/tests/preference2/res/xml/pref_nested.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <PreferenceCategory
+ android:key="pref_category"
+ android:title="@string/launch_preferences">
+
+ <PreferenceScreen
+ android:key="pref_screen_inner"
+ android:title="@string/title_screen_preference"
+ android:summary="@string/summary_screen_preference">
+
+ <CheckBoxPreference
+ android:key="pref_checkbox"
+ android:title="Test"
+ android:summary="Test" />
+
+ </PreferenceScreen>
+
+ </PreferenceCategory>
+</PreferenceScreen>
diff --git a/tests/tests/preference2/src/android/preference2/cts/PreferenceParentGroupTest.java b/tests/tests/preference2/src/android/preference2/cts/PreferenceParentGroupTest.java
new file mode 100644
index 0000000..5de6e81
--- /dev/null
+++ b/tests/tests/preference2/src/android/preference2/cts/PreferenceParentGroupTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.preference2.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.preference.CheckBoxPreference;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceScreen;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link android.preference.Preference#getParent()} feature.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreferenceParentGroupTest {
+
+ private PreferencesFromXmlNested mActivity;
+
+ @Rule
+ public ActivityTestRule<PreferencesFromXmlNested> mActivityRule =
+ new ActivityTestRule<>(PreferencesFromXmlNested.class);
+
+
+ @Before
+ public void setup() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ /**
+ * Tests that parent PreferenceGroup is correctly assigned and removed when creating preferences
+ * from code.
+ */
+ @Test
+ public void parentViaCodeTest() {
+ PreferenceScreen screen = mActivity.getPreferenceScreen();
+ assertNull(screen.getParent());
+
+ PreferenceCategory category = new PreferenceCategory(mActivity);
+ assertNull(category.getParent());
+
+ CheckBoxPreference pref = new CheckBoxPreference(mActivity);
+ assertNull(pref.getParent());
+
+ screen.addPreference(category);
+ assertEquals(screen, category.getParent());
+
+ category.addPreference(pref);
+ assertEquals(category, pref.getParent());
+
+ screen.removePreference(category);
+ assertNull(category.getParent());
+
+ category.removePreference(pref);
+ assertNull(pref.getParent());
+ }
+
+ /**
+ * Tests that parent PreferenceGroup is correctly assigned during inflation and can be modified.
+ * To see the tested hierarchy check pref_nested.xml.
+ */
+ @Test
+ public void parentViaInflationTest() {
+ PreferenceScreen screen = mActivity.getPreferenceScreen();
+
+ PreferenceCategory category = (PreferenceCategory) screen.findPreference("pref_category");
+ assertNotNull(category);
+
+ PreferenceScreen screenInner =
+ (PreferenceScreen) screen.findPreference("pref_screen_inner");
+ assertNotNull(screenInner);
+
+ CheckBoxPreference pref = (CheckBoxPreference) screen.findPreference("pref_checkbox");
+ assertNotNull(pref);
+
+ // Validate parents
+ assertEquals(screen, category.getParent());
+ assertEquals(category, screenInner.getParent());
+ assertEquals(screenInner, pref.getParent());
+
+ // Remove and validate
+ pref.getParent().removePreference(pref);
+ assertNull(pref.getParent());
+ assertEquals(0, screenInner.getPreferenceCount());
+ }
+
+ /**
+ * Adds preference into two different groups without removing it first.
+ */
+ @Test
+ public void parentDoubleAddTest() throws InterruptedException {
+ PreferenceScreen screen = mActivity.getPreferenceScreen();
+
+ PreferenceCategory category = new PreferenceCategory(mActivity);
+ screen.addPreference(category);
+
+ PreferenceCategory category2 = new PreferenceCategory(mActivity);
+ screen.addPreference(category2);
+
+ CheckBoxPreference pref = new CheckBoxPreference(mActivity);
+ assertNull(pref.getParent());
+
+ category.addPreference(pref);
+ category2.addPreference(pref);
+
+ assertEquals(category2, pref.getParent());
+ }
+}
diff --git a/tests/tests/preference2/src/android/preference2/cts/PreferencesFromXmlNested.java b/tests/tests/preference2/src/android/preference2/cts/PreferencesFromXmlNested.java
new file mode 100644
index 0000000..df31c1e
--- /dev/null
+++ b/tests/tests/preference2/src/android/preference2/cts/PreferencesFromXmlNested.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.preference2.cts;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.preference2.cts.R;
+
+public class PreferencesFromXmlNested extends PreferenceActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_nested);
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/VoicemailContractTest.java b/tests/tests/provider/src/android/provider/cts/VoicemailContractTest.java
index 8e0a6cc..6dd4d3c 100644
--- a/tests/tests/provider/src/android/provider/cts/VoicemailContractTest.java
+++ b/tests/tests/provider/src/android/provider/cts/VoicemailContractTest.java
@@ -63,7 +63,11 @@
final String[] VOICEMAILS_PROJECTION = new String[] {
Voicemails._ID, Voicemails.NUMBER, Voicemails.DATE, Voicemails.DURATION,
Voicemails.IS_READ, Voicemails.SOURCE_PACKAGE, Voicemails.SOURCE_DATA,
- Voicemails.HAS_CONTENT, Voicemails.MIME_TYPE};
+ Voicemails.HAS_CONTENT, Voicemails.MIME_TYPE, Voicemails.TRANSCRIPTION,
+ Voicemails.PHONE_ACCOUNT_COMPONENT_NAME,
+ Voicemails.PHONE_ACCOUNT_ID, Voicemails.DIRTY, Voicemails.DELETED,
+ Voicemails.LAST_MODIFIED, Voicemails.BACKED_UP, Voicemails.RESTORED,
+ Voicemails.ARCHIVED, Voicemails.IS_OMTP_VOICEMAIL};
final int ID_INDEX = 0;
final int NUMBER_INDEX = 1;
final int DATE_INDEX = 2;
@@ -73,6 +77,16 @@
final int SOURCE_DATA_INDEX = 6;
final int HAS_CONTENT_INDEX = 7;
final int MIME_TYPE_INDEX = 8;
+ final int TRANSCRIPTION_INDEX= 9;
+ final int PHONE_ACCOUNT_COMPONENT_NAME_INDEX = 10;
+ final int PHONE_ACCOUNT_ID_INDEX = 11;
+ final int DIRTY_INDEX = 12;
+ final int DELETED_INDEX = 13;
+ final int LAST_MODIFIED_INDEX = 14;
+ final int BACKED_UP_INDEX = 15;
+ final int RESTORED_INDEX = 16;
+ final int ARCHIVED_INDEX = 17;
+ final int IS_OMTP_VOICEMAIL_INDEX = 18;
String insertCallsNumber = "0123456789";
long insertCallsDuration = 120;
@@ -95,6 +109,15 @@
value.put(Voicemails.MIME_TYPE, insertMimeType);
value.put(Voicemails.IS_READ, false);
value.put(Voicemails.HAS_CONTENT, true);
+ value.put(Voicemails.TRANSCRIPTION, "foo");
+ value.put(Voicemails.PHONE_ACCOUNT_COMPONENT_NAME, "com.foo");
+ value.put(Voicemails.PHONE_ACCOUNT_ID, "bar");
+ value.put(Voicemails.DIRTY, 0);
+ value.put(Voicemails.DELETED, 0);
+ value.put(Voicemails.BACKED_UP, 0);
+ value.put(Voicemails.RESTORED, 0);
+ value.put(Voicemails.ARCHIVED, 0);
+ value.put(Voicemails.IS_OMTP_VOICEMAIL, 0);
Uri uri = mVoicemailProvider.insert(mVoicemailContentUri, value);
Cursor cursor = mVoicemailProvider.query(
@@ -110,6 +133,15 @@
assertEquals(insertMimeType, cursor.getString(MIME_TYPE_INDEX));
assertEquals(0, cursor.getInt(IS_READ_INDEX));
assertEquals(1, cursor.getInt(HAS_CONTENT_INDEX));
+ assertEquals("foo",cursor.getString(TRANSCRIPTION_INDEX));
+ assertEquals("com.foo",cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_INDEX));
+ assertEquals("bar",cursor.getString(PHONE_ACCOUNT_ID_INDEX));
+ assertEquals(0,cursor.getInt(DIRTY_INDEX));
+ assertEquals(0,cursor.getInt(DELETED_INDEX));
+ assertEquals(0,cursor.getInt(BACKED_UP_INDEX));
+ assertEquals(0,cursor.getInt(RESTORED_INDEX));
+ assertEquals(0,cursor.getInt(ARCHIVED_INDEX));
+ assertEquals(0,cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
int id = cursor.getInt(ID_INDEX);
assertEquals(id, Integer.parseInt(uri.getLastPathSegment()));
cursor.close();
@@ -120,6 +152,12 @@
value.put(Voicemails.DATE, updateDate);
value.put(Voicemails.DURATION, updateCallsDuration);
value.put(Voicemails.SOURCE_DATA, updateSourceData);
+ value.put(Voicemails.DIRTY, 1);
+ value.put(Voicemails.DELETED, 1);
+ value.put(Voicemails.BACKED_UP, 1);
+ value.put(Voicemails.RESTORED, 1);
+ value.put(Voicemails.ARCHIVED, 1);
+ value.put(Voicemails.IS_OMTP_VOICEMAIL, 1);
mVoicemailProvider.update(uri, value, null, null);
cursor = mVoicemailProvider.query(mVoicemailContentUri, VOICEMAILS_PROJECTION,
@@ -131,6 +169,12 @@
assertEquals(updateDate, cursor.getLong(DATE_INDEX));
assertEquals(updateCallsDuration, cursor.getLong(DURATION_INDEX));
assertEquals(updateSourceData, cursor.getString(SOURCE_DATA_INDEX));
+ assertEquals(1,cursor.getInt(DIRTY_INDEX));
+ assertEquals(1,cursor.getInt(DELETED_INDEX));
+ assertEquals(1,cursor.getInt(BACKED_UP_INDEX));
+ assertEquals(1,cursor.getInt(RESTORED_INDEX));
+ assertEquals(1,cursor.getInt(ARCHIVED_INDEX));
+ assertEquals(1,cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
cursor.close();
// Test: delete
diff --git a/tests/tests/security/src/android/security/cts/AslrTest.java b/tests/tests/security/src/android/security/cts/AslrTest.java
index 2cc4825..a28498b 100644
--- a/tests/tests/security/src/android/security/cts/AslrTest.java
+++ b/tests/tests/security/src/android/security/cts/AslrTest.java
@@ -96,8 +96,6 @@
public void testRandomization() throws Exception {
testMappingEntropy("stack");
- testMappingEntropy("heap");
- testMappingEntropy("anon:libc_malloc");
}
public void testOneExecutableIsPie() throws IOException {
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index d167249..1a3206f 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -18,7 +18,7 @@
import static android.telecom.cts.TestUtils.*;
-import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import android.graphics.drawable.Icon;
@@ -131,7 +131,7 @@
return;
}
- assertThat(mCall.getDetails().getAccountHandle(), is(PhoneAccountHandle.class));
+ assertThat(mCall.getDetails().getAccountHandle(), instanceOf(PhoneAccountHandle.class));
assertEquals(TEST_PHONE_ACCOUNT_HANDLE, mCall.getDetails().getAccountHandle());
}
@@ -143,7 +143,7 @@
return;
}
- assertThat(mCall.getDetails().getCallCapabilities(), is(Integer.class));
+ assertThat(mCall.getDetails().getCallCapabilities(), instanceOf(Integer.class));
assertEquals(CALL_CAPABILITIES, mCall.getDetails().getCallCapabilities());
assertTrue(mCall.getDetails().can(Call.Details.CAPABILITY_HOLD));
assertTrue(mCall.getDetails().can(Call.Details.CAPABILITY_MUTE));
@@ -248,7 +248,7 @@
return;
}
- assertThat(mCall.getDetails().getCallerDisplayName(), is(String.class));
+ assertThat(mCall.getDetails().getCallerDisplayName(), instanceOf(String.class));
assertEquals(CALLER_DISPLAY_NAME, mCall.getDetails().getCallerDisplayName());
}
@@ -260,7 +260,7 @@
return;
}
- assertThat(mCall.getDetails().getCallerDisplayNamePresentation(), is(Integer.class));
+ assertThat(mCall.getDetails().getCallerDisplayNamePresentation(), instanceOf(Integer.class));
assertEquals(CALLER_DISPLAY_NAME_PRESENTATION, mCall.getDetails().getCallerDisplayNamePresentation());
}
@@ -272,7 +272,7 @@
return;
}
- assertThat(mCall.getDetails().getCallProperties(), is(Integer.class));
+ assertThat(mCall.getDetails().getCallProperties(), instanceOf(Integer.class));
assertEquals(CALL_PROPERTIES, mCall.getDetails().getCallProperties());
}
@@ -285,7 +285,7 @@
return;
}
- assertThat(mCall.getDetails().getConnectTimeMillis(), is(Long.class));
+ assertThat(mCall.getDetails().getConnectTimeMillis(), instanceOf(Long.class));
}
/**
@@ -296,7 +296,7 @@
return;
}
- assertThat(mCall.getDetails().getDisconnectCause(), is(DisconnectCause.class));
+ assertThat(mCall.getDetails().getDisconnectCause(), instanceOf(DisconnectCause.class));
}
/**
@@ -308,7 +308,7 @@
}
if (mCall.getDetails().getExtras() != null) {
- assertThat(mCall.getDetails().getExtras(), is(Bundle.class));
+ assertThat(mCall.getDetails().getExtras(), instanceOf(Bundle.class));
}
}
@@ -320,7 +320,7 @@
return;
}
- assertThat(mCall.getDetails().getIntentExtras(), is(Bundle.class));
+ assertThat(mCall.getDetails().getIntentExtras(), instanceOf(Bundle.class));
}
/**
@@ -332,7 +332,7 @@
}
if (mCall.getDetails().getGatewayInfo() != null) {
- assertThat(mCall.getDetails().getGatewayInfo(), is(GatewayInfo.class));
+ assertThat(mCall.getDetails().getGatewayInfo(), instanceOf(GatewayInfo.class));
}
}
@@ -344,7 +344,7 @@
return;
}
- assertThat(mCall.getDetails().getHandle(), is(Uri.class));
+ assertThat(mCall.getDetails().getHandle(), instanceOf(Uri.class));
assertEquals(getTestNumber(), mCall.getDetails().getHandle());
}
@@ -356,7 +356,7 @@
return;
}
- assertThat(mCall.getDetails().getHandlePresentation(), is(Integer.class));
+ assertThat(mCall.getDetails().getHandlePresentation(), instanceOf(Integer.class));
assertEquals(MockConnectionService.CONNECTION_PRESENTATION, mCall.getDetails().getHandlePresentation());
}
@@ -368,7 +368,7 @@
return;
}
- assertThat(mCall.getDetails().getStatusHints(), is(StatusHints.class));
+ assertThat(mCall.getDetails().getStatusHints(), instanceOf(StatusHints.class));
assertEquals(mStatusHints.getLabel(), mCall.getDetails().getStatusHints().getLabel());
assertEquals(
mStatusHints.getIcon().toString(),
@@ -384,7 +384,7 @@
return;
}
- assertThat(mCall.getDetails().getVideoState(), is(Integer.class));
+ assertThat(mCall.getDetails().getVideoState(), instanceOf(Integer.class));
}
/**
diff --git a/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java b/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java
index eaeea3e..558ef32 100644
--- a/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java
@@ -35,6 +35,7 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
import android.telephony.VisualVoicemailService;
import android.telephony.VisualVoicemailSms;
import android.telephony.VisualVoicemailSmsFilterSettings;
@@ -253,6 +254,15 @@
assertEquals("", result.getFields().getString("key"));
}
+ public void testGetVisualVoicemailPackageName_isSelf(){
+ if (!hasTelephony(mContext)) {
+ Log.d(TAG, "skipping test that requires telephony feature");
+ return;
+ }
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ assertEquals(PACKAGE, telephonyManager.getVisualVoicemailPackageName(mPhoneAccountHandle));
+ }
+
private VisualVoicemailSms getSmsFromText(String clientPrefix, String text) {
return getSmsFromText(clientPrefix, text, true);
}
diff --git a/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java b/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java
index 70a68ea..3e58853 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java
@@ -629,6 +629,79 @@
}
@Test
+ public void testGetSpans_returnsInInsertionOrder_regular() {
+ assertGetSpans_returnsInInsertionOrder(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ @Test
+ public void testGetSpans_returnsInInsertionOrder_priority() {
+ assertGetSpans_returnsInInsertionOrder(
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_PRIORITY);
+ }
+
+ private static void assertGetSpans_returnsInInsertionOrder(int flags) {
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+ final SubscriptSpan[] expected = new SubscriptSpan[5];
+ for (int i = 0; i < expected.length; i++) {
+ final int currentLength = builder.length();
+ builder.append("12\n");
+ expected[i] = new SubscriptSpan();
+ builder.setSpan(expected[i], currentLength + 1, currentLength + 2, flags);
+ }
+
+ final SubscriptSpan[] spans = builder.getSpans(0, builder.length(), SubscriptSpan.class);
+
+ assertNotNull(spans);
+ assertEquals(expected.length, spans.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertSame(expected[i], spans[i]);
+ }
+ }
+
+ @Test
+ public void testGetSpans_returnsInInsertionOrder_priorityAndRegular() {
+ // insert spans from end to start of the string, interleaved. for each alternation:
+ // * regular span start is less than the priority span;
+ // * setSpan for regular span is called before the priority span
+ // expected result is: priority spans in the insertion order, then regular spans in
+ // insertion order
+ final int arrayLength = 5;
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+ final SubscriptSpan[] regularSpans = new SubscriptSpan[arrayLength];
+ final SubscriptSpan[] prioritySpans = new SubscriptSpan[arrayLength];
+ for (int i = 0; i < arrayLength; i++) {
+ builder.append("12");
+ regularSpans[i] = new SubscriptSpan();
+ prioritySpans[i] = new SubscriptSpan();
+ }
+
+ // set spans on builder with pattern [regular, priority, regular, priority,...]
+ // in reverse order (regularSpan[0] is at the end of the builder, regularSpan[5] is at the
+ // begining.
+ for (int i = 1; i <= arrayLength; i++) {
+ final int spanStart = builder.length() - (i * 2);
+ builder.setSpan(regularSpans[i - 1], spanStart, spanStart + 1,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ builder.setSpan(prioritySpans[i - 1], spanStart + 1, spanStart + 2,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_PRIORITY);
+ }
+
+ final SubscriptSpan[] spans = builder.getSpans(0, builder.length(), SubscriptSpan.class);
+ assertNotNull(spans);
+ assertEquals(arrayLength * 2, spans.length);
+
+ // assert priority spans are returned before the regular spans, in the insertion order
+ for (int i = 0; i < arrayLength; i++) {
+ assertSame(prioritySpans[i], spans[i]);
+ }
+
+ // assert regular spans are returned after priority spans, in the insertion order
+ for (int i = 0; i < arrayLength; i++) {
+ assertSame(regularSpans[i], spans[i + arrayLength]);
+ }
+ }
+
+ @Test
public void testGetSpans_returnsSpansInInsertionOrderWhenTheLaterCoversTheFirst() {
String text = "p_in_s";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index f1e883c..da51c74 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -1196,8 +1196,8 @@
assertEquals(0, layout.getLineForOffset(lineBreakOffset - 1));
assertEquals(0, layout.getOffsetForHorizontal(0, 0.0f));
- assertEquals(lineBreakOffset - 2, layout.getOffsetForHorizontal(0, width));
- assertEquals(lineBreakOffset - 2, layout.getOffsetForHorizontal(0, width * 2));
+ assertEquals(lineBreakOffset, layout.getOffsetForHorizontal(0, width));
+ assertEquals(lineBreakOffset, layout.getOffsetForHorizontal(0, width * 2));
final int lineCount = layout.getLineCount();
assertEquals(testString.length(), layout.getOffsetForHorizontal(lineCount - 1, width));
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
index 8811748..6cd4f4e 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
@@ -140,10 +140,11 @@
super.tearDown();
}
- private static ContentValues createDummyChannelValues(String inputId) {
+ private static ContentValues createDummyChannelValues(String inputId, boolean preview) {
ContentValues values = new ContentValues();
values.put(TvContract.Channels.COLUMN_INPUT_ID, inputId);
- values.put(TvContract.Channels.COLUMN_TYPE, TvContract.Channels.TYPE_OTHER);
+ values.put(TvContract.Channels.COLUMN_TYPE,
+ preview ? TvContract.Channels.TYPE_PREVIEW : TvContract.Channels.TYPE_OTHER);
values.put(TvContract.Channels.COLUMN_SERVICE_TYPE,
TvContract.Channels.SERVICE_TYPE_AUDIO_VIDEO);
values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, "1");
@@ -168,6 +169,31 @@
return values;
}
+ private static ContentValues createDummyPreviewProgramValues(long channelId) {
+ ContentValues values = new ContentValues();
+ values.put(TvContract.Programs.COLUMN_CHANNEL_ID, channelId);
+ values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_ID, "ID-4321");
+ values.put(TvContract.Programs.COLUMN_PREVIEW_VIDEO_URI, "http://test.com/preview.mp4");
+ values.put(TvContract.Programs.COLUMN_PREVIEW_LAST_PLAYBACK_POSITION, 5000);
+ values.put(TvContract.Programs.COLUMN_PREVIEW_DURATION, 60000);
+ values.put(TvContract.Programs.COLUMN_PREVIEW_INTENT_URI,
+ "preview_app_link_intent");
+ values.put(TvContract.Programs.COLUMN_PREVIEW_WEIGHT, 100);
+ values.put(TvContract.Programs.COLUMN_TITLE, "program_title");
+ values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, "short_description");
+ values.put(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
+ values.put(TvContract.Programs.COLUMN_EPISODE_TITLE, "episode_title");
+ values.put(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
+ values.put(TvContract.Programs.COLUMN_SEASON_TITLE, "season_title");
+ values.put(TvContract.Programs.COLUMN_CANONICAL_GENRE, TvContract.Programs.Genres.encode(
+ TvContract.Programs.Genres.SPORTS, TvContract.Programs.Genres.DRAMA));
+ TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
+ "US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
+ values.put(TvContract.Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
+
+ return values;
+ }
+
private static ContentValues createDummyRecordedProgramValues(String inputId, long channelId) {
ContentValues values = new ContentValues();
values.put(TvContract.RecordedPrograms.COLUMN_INPUT_ID, inputId);
@@ -275,7 +301,7 @@
return;
}
// Test: insert
- ContentValues values = createDummyChannelValues(mInputId);
+ ContentValues values = createDummyChannelValues(mInputId, false);
Uri rowUri = mContentResolver.insert(mChannelsUri, values);
long channelId = ContentUris.parseId(rowUri);
@@ -300,7 +326,7 @@
private void verifyProgram(Uri programUri, ContentValues expectedValues, long programId) {
try (Cursor cursor = mContentResolver.query(
- programUri, PROGRAMS_PROJECTION, null, null, null)) {
+ programUri, null, null, null, null)) {
assertNotNull(cursor);
assertEquals(cursor.getCount(), 1);
assertTrue(cursor.moveToNext());
@@ -331,6 +357,16 @@
verifyBlobColumn(cursor, expectedValues,
TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA);
verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_VERSION_NUMBER);
+
+ verifyStringColumn(cursor, expectedValues,
+ TvContract.Programs.COLUMN_INTERNAL_PROVIDER_ID);
+ verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_PREVIEW_VIDEO_URI);
+ verifyIntegerColumn(cursor, expectedValues,
+ TvContract.Programs.COLUMN_PREVIEW_LAST_PLAYBACK_POSITION);
+ verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_PREVIEW_DURATION);
+ verifyStringColumn(cursor, expectedValues,
+ TvContract.Programs.COLUMN_PREVIEW_INTENT_URI);
+ verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_PREVIEW_WEIGHT);
}
}
@@ -363,7 +399,7 @@
return;
}
// Set-up: add a channel.
- ContentValues values = createDummyChannelValues(mInputId);
+ ContentValues values = createDummyChannelValues(mInputId, false);
Uri channelUri = mContentResolver.insert(mChannelsUri, values);
Uri logoUri = TvContract.buildChannelLogoUri(channelUri);
Bitmap logo = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.robot);
@@ -398,6 +434,33 @@
verifyProgram(programUri, values, programId);
// Test: update
+ values.put(TvContract.Programs.COLUMN_TITLE, "new_program_title");
+ values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, "");
+ values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
+
+ mContentResolver.update(programUri, values, null, null);
+ verifyProgram(programUri, values, programId);
+
+ // Test: delete
+ mContentResolver.delete(programsUri, null, null);
+ try (Cursor cursor = mContentResolver.query(programsUri, null, null, null, null)) {
+ assertEquals(0, cursor.getCount());
+ }
+ }
+
+ public void verifyProgramsTableWithPreviewTypeEntry(Uri programsUri, long channelId) {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ // Test: insert
+ ContentValues values = createDummyPreviewProgramValues(channelId);
+
+ Uri rowUri = mContentResolver.insert(programsUri, values);
+ long programId = ContentUris.parseId(rowUri);
+ Uri programUri = TvContract.buildProgramUri(programId);
+ verifyProgram(programUri, values, programId);
+
+ // Test: update
values.put(TvContract.Programs.COLUMN_EPISODE_TITLE, "Sample title");
values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, "Short description");
values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
@@ -457,7 +520,7 @@
return;
}
// Set-up: add a channel.
- ContentValues values = createDummyChannelValues(mInputId);
+ ContentValues values = createDummyChannelValues(mInputId, false);
Uri channelUri = mContentResolver.insert(mChannelsUri, values);
long channelId = ContentUris.parseId(channelUri);
@@ -469,6 +532,19 @@
channelId);
}
+ public void testProgramsTableWithPreviewType() throws Exception {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ // Set-up: add a preview type channel.
+ ContentValues values = createDummyChannelValues(mInputId, true);
+ Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+ long channelId = ContentUris.parseId(channelUri);
+
+ verifyProgramsTableWithPreviewTypeEntry(TvContract.buildProgramsUriForChannel(channelUri),
+ channelId);
+ }
+
private void verifyOverlap(long startMillis, long endMillis, int expectedCount,
long channelId, Uri channelUri) {
try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(
@@ -490,7 +566,7 @@
final long hour = 3600000l;
// Set-up: add a channel and program.
- ContentValues values = createDummyChannelValues(mInputId);
+ ContentValues values = createDummyChannelValues(mInputId, false);
Uri channelUri = mContentResolver.insert(mChannelsUri, values);
long channelId = ContentUris.parseId(channelUri);
Uri programsUri = TvContract.buildProgramsUriForChannel(channelId);
@@ -600,7 +676,7 @@
return;
}
// Set-up: add a channel.
- ContentValues values = createDummyChannelValues(mInputId);
+ ContentValues values = createDummyChannelValues(mInputId, false);
Uri channelUri = mContentResolver.insert(mChannelsUri, values);
long channelId = ContentUris.parseId(channelUri);
@@ -890,7 +966,7 @@
}
private Uri insertProgramWithBroadcastGenre(String[] broadcastGenre) {
- ContentValues values = createDummyChannelValues(mInputId);
+ ContentValues values = createDummyChannelValues(mInputId, false);
Uri channelUri = mContentResolver.insert(Channels.CONTENT_URI, values);
long channelId = ContentUris.parseId(channelUri);
long curTime = System.currentTimeMillis();
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index edea284..54f2612 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -261,6 +261,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <activity android:name="android.view.cts.PointerCaptureCtsActivity"
+ android:theme="@style/WhiteBackgroundTheme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/res/layout/focus_handling_auto_layout.xml b/tests/tests/view/res/layout/focus_handling_auto_layout.xml
new file mode 100644
index 0000000..07b4423
--- /dev/null
+++ b/tests/tests/view/res/layout/focus_handling_auto_layout.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/focusableauto"
+ android:focusable="auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <View
+ android:id="@+id/focusabledefault"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <View
+ android:id="@+id/onlyclickable"
+ android:clickable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <View
+ android:id="@+id/clickablenotfocusable"
+ android:clickable="true"
+ android:focusable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/view/res/layout/focus_handling_layout.xml b/tests/tests/view/res/layout/focus_handling_layout.xml
index 84c8e6a..4c0178a 100644
--- a/tests/tests/view/res/layout/focus_handling_layout.xml
+++ b/tests/tests/view/res/layout/focus_handling_layout.xml
@@ -53,5 +53,15 @@
android:nextFocusUp="@id/view2"
android:nextFocusLeft="@id/view3"
android:text="@string/id_ok"/>
+
+ <LinearLayout
+ android:id="@+id/auto_test_area"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_below="@id/view4"
+ android:layout_alignParentLeft="true">
+ </LinearLayout>
+
</RelativeLayout>
diff --git a/tests/tests/view/res/layout/pointer_capture_layout.xml b/tests/tests/view/res/layout/pointer_capture_layout.xml
new file mode 100644
index 0000000..1e40070
--- /dev/null
+++ b/tests/tests/view/res/layout/pointer_capture_layout.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/top"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/outer"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="20dp"
+ android:gravity="center"
+ android:background="#eee">
+
+ <LinearLayout
+ android:id="@+id/inner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="20dp"
+ android:background="#ddd">
+
+ <LinearLayout
+ android:id="@+id/target"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:layout_margin="20dp"
+ android:background="#bbb"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/target2"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:layout_margin="40dp"
+ android:background="#bbb"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/tests/view/src/android/view/cts/HoverTest.java b/tests/tests/view/src/android/view/cts/HoverTest.java
index 24e3020..45b56e9 100644
--- a/tests/tests/view/src/android/view/cts/HoverTest.java
+++ b/tests/tests/view/src/android/view/cts/HoverTest.java
@@ -16,31 +16,35 @@
package android.view.cts;
+import static com.android.compatibility.common.util.CtsMouseUtil.clearHoverListener;
+import static com.android.compatibility.common.util.CtsMouseUtil.installHoverListener;
+import static com.android.compatibility.common.util.CtsMouseUtil.obtainMouseEvent;
+import static com.android.compatibility.common.util.CtsMouseUtil.verifyEnterMove;
+import static com.android.compatibility.common.util.CtsMouseUtil.verifyEnterMoveExit;
+
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.Activity;
import android.app.Instrumentation;
-import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import org.hamcrest.Description;
+import com.android.compatibility.common.util.CtsMouseUtil.ActionMatcher;
+import com.android.compatibility.common.util.CtsMouseUtil.PositionMatcher;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
/**
@@ -73,48 +77,6 @@
public ActivityTestRule<HoverCtsActivity> mActivityRule =
new ActivityTestRule<>(HoverCtsActivity.class);
- static class ActionMatcher extends ArgumentMatcher<MotionEvent> {
- private final int mAction;
-
- ActionMatcher(int action) {
- mAction = action;
- }
-
- public boolean matches(Object actual) {
- return (actual instanceof MotionEvent) && ((MotionEvent) actual).getAction() == mAction;
- }
-
- public void describeTo(Description description) {
- description.appendText("action=" + MotionEvent.actionToString(mAction));
- }
- }
-
- static class MoveMatcher extends ActionMatcher {
- private final int mX;
- private final int mY;
-
- MoveMatcher(int x, int y) {
- super(MotionEvent.ACTION_HOVER_MOVE);
- mX = x;
- mY = y;
- }
-
- public boolean matches(Object actual) {
- return super.matches(actual)
- && ((int)((MotionEvent)actual).getX()) == mX
- && ((int)((MotionEvent)actual).getY()) == mY;
- }
-
- public void describeTo(Description description) {
- super.describeTo(description);
- description.appendText("@(" + mX + "," + mY + ")");
- }
- }
-
- private final ActionMatcher mEnterMatcher = new ActionMatcher(MotionEvent.ACTION_HOVER_ENTER);
- private final ActionMatcher mMoveMatcher = new ActionMatcher(MotionEvent.ACTION_HOVER_MOVE);
- private final ActionMatcher mExitMatcher = new ActionMatcher(MotionEvent.ACTION_HOVER_EXIT);
-
@Before
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -136,64 +98,16 @@
mLayer4Right = mActivity.findViewById(R.id.layer4_right);
}
- private void verifyHoverSequence(
- View.OnHoverListener listener, View view, int moveCount, boolean exit) {
- InOrder inOrder = inOrder(listener);
- inOrder.verify(listener, times(1)).onHover(eq(view), argThat(mEnterMatcher));
- inOrder.verify(listener, times(moveCount)).onHover(eq(view), argThat(mMoveMatcher));
- if (exit) {
- inOrder.verify(listener, times(1)).onHover(eq(view), argThat(mExitMatcher));
- }
- verifyNoMoreInteractions(listener);
- }
-
- private void verifyEnterMove(View.OnHoverListener listener, View view, int moveCount) {
- verifyHoverSequence(listener, view, moveCount, false);
- }
-
- private void verifyEnterMoveExit(View.OnHoverListener listener, View view, int moveCount) {
- verifyHoverSequence(listener, view, moveCount, true);
- }
-
private void injectHoverMove(View view) {
injectHoverMove(view, 0, 0);
}
private void injectHoverMove(View view, int offsetX, int offsetY) {
mActivity.getWindow().injectInputEvent(
- obtainMotionEvent(view, MotionEvent.ACTION_HOVER_MOVE, offsetX, offsetY));
+ obtainMouseEvent(MotionEvent.ACTION_HOVER_MOVE, view, offsetX, offsetY));
mInstrumentation.waitForIdleSync();
}
- private MotionEvent obtainMotionEvent(View anchor, int action, int offsetX, int offsetY) {
- final long eventTime = SystemClock.uptimeMillis();
- final int[] screenPos = new int[2];
- anchor.getLocationOnScreen(screenPos);
- final int x = screenPos[0] + offsetX;
- final int y = screenPos[1] + offsetY;
- MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0);
- event.setSource(InputDevice.SOURCE_MOUSE);
- return event;
- }
-
- private View.OnHoverListener installHoverListener(View view) {
- return installHoverListener(view, true);
- }
-
- private View.OnHoverListener installHoverListener(View view, boolean result) {
- final View.OnHoverListener mockListener = mock(View.OnHoverListener.class);
- view.setOnHoverListener((v, event) -> {
- // Clone the event to work around event instance reuse in the framework.
- mockListener.onHover(v, MotionEvent.obtain(event));
- return result;
- });
- return mockListener;
- }
-
- private void clearHoverListener(View view) {
- view.setOnHoverListener(null);
- }
-
private void remove(View view) throws Throwable {
mActivityRule.runOnUiThread(() -> ((ViewGroup)view.getParent()).removeView(view));
}
@@ -221,10 +135,14 @@
InOrder inOrder = inOrder(listener);
- inOrder.verify(listener, times(1)).onHover(eq(mInner11), argThat(mEnterMatcher));
- inOrder.verify(listener, times(1)).onHover(eq(mInner11), argThat(new MoveMatcher(1, 2)));
- inOrder.verify(listener, times(1)).onHover(eq(mInner11), argThat(new MoveMatcher(3, 4)));
- inOrder.verify(listener, times(1)).onHover(eq(mInner11), argThat(new MoveMatcher(5, 6)));
+ inOrder.verify(listener, times(1)).onHover(eq(mInner11),
+ argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_ENTER)));
+ inOrder.verify(listener, times(1)).onHover(eq(mInner11),
+ argThat(new PositionMatcher(MotionEvent.ACTION_HOVER_MOVE, 1, 2)));
+ inOrder.verify(listener, times(1)).onHover(eq(mInner11),
+ argThat(new PositionMatcher(MotionEvent.ACTION_HOVER_MOVE, 3, 4)));
+ inOrder.verify(listener, times(1)).onHover(eq(mInner11),
+ argThat(new PositionMatcher(MotionEvent.ACTION_HOVER_MOVE, 5, 6)));
verifyNoMoreInteractions(listener);
}
diff --git a/tests/tests/view/src/android/view/cts/PointerCaptureCtsActivity.java b/tests/tests/view/src/android/view/cts/PointerCaptureCtsActivity.java
new file mode 100644
index 0000000..b9f42de
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/PointerCaptureCtsActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.View;
+
+public class PointerCaptureCtsActivity extends Activity {
+ private boolean mHasCapture;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pointer_capture_layout);
+ }
+
+ public void onCreateContextMenu(
+ ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ menu.add("context menu");
+ }
+
+ @Override
+ public void onPointerCaptureChanged(boolean hasCapture) {
+ mHasCapture = hasCapture;
+ }
+
+ public boolean hasPointerCapture() {
+ return mHasCapture;
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/PointerCaptureTest.java b/tests/tests/view/src/android/view/cts/PointerCaptureTest.java
new file mode 100644
index 0000000..f539d00
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/PointerCaptureTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static com.android.compatibility.common.util.CtsMouseUtil.PositionMatcher;
+import static com.android.compatibility.common.util.CtsMouseUtil.clearHoverListener;
+import static com.android.compatibility.common.util.CtsMouseUtil.installHoverListener;
+import static com.android.compatibility.common.util.CtsMouseUtil.obtainMouseEvent;
+import static com.android.compatibility.common.util.CtsMouseUtil.verifyEnterMove;
+import static com.android.compatibility.common.util.CtsMouseUtil.verifyEnterMoveExit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.app.Instrumentation;
+import android.hardware.input.InputManager;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.compatibility.common.util.CtsMouseUtil.ActionMatcher;
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
+/**
+ * Test {@link View}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PointerCaptureTest {
+ private static final long TIMEOUT_DELTA = 10000;
+
+ private Instrumentation mInstrumentation;
+ private PointerCaptureCtsActivity mActivity;
+
+ private View mOuter;
+ private View mInner;
+ private View mTarget;
+ private View mTarget2;
+
+ @Rule
+ public ActivityTestRule<PointerCaptureCtsActivity> mActivityRule =
+ new ActivityTestRule<>(PointerCaptureCtsActivity.class);
+
+ @Rule
+ public ActivityTestRule<CtsActivity> mCtsActivityRule =
+ new ActivityTestRule<>(CtsActivity.class, false, false);
+
+ private void requestFocusSync(View view) throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ view.setFocusable(true);
+ view.setFocusableInTouchMode(true);
+ view.requestFocus();
+ });
+ PollingCheck.waitFor(TIMEOUT_DELTA, view::hasFocus);
+ }
+
+ private void requestCaptureSync(View view) throws Throwable {
+ mActivityRule.runOnUiThread(view::requestPointerCapture);
+ PollingCheck.waitFor(TIMEOUT_DELTA, view::hasPointerCapture);
+ }
+
+ private void requestCaptureSync() throws Throwable {
+ requestCaptureSync(mOuter);
+ }
+
+ private void releaseCaptureSync(View view) throws Throwable {
+ mActivityRule.runOnUiThread(view::releasePointerCapture);
+ PollingCheck.waitFor(TIMEOUT_DELTA, () -> !view.hasPointerCapture());
+ }
+
+ private void releaseCaptureSync() throws Throwable {
+ releaseCaptureSync(mOuter);
+ }
+
+ public static View.OnCapturedPointerListener installCapturedPointerListener(View view) {
+ final View.OnCapturedPointerListener mockListener =
+ mock(View.OnCapturedPointerListener.class);
+ view.setOnCapturedPointerListener((v, event) -> {
+ // Clone the event to work around event instance reuse in the framework.
+ mockListener.onCapturedPointer(v, MotionEvent.obtain(event));
+ return true;
+ });
+ return mockListener;
+ }
+
+ public static void clearCapturedPointerListener(View view) {
+ view.setOnCapturedPointerListener(null);
+ }
+
+ private static void injectMotionEvent(MotionEvent event) {
+ InputManager.getInstance().injectInputEvent(event,
+ InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+ }
+
+ private static void injectRelativeMouseEvent(int action, int x, int y) {
+ final long eventTime = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0);
+ event.setSource(InputDevice.SOURCE_MOUSE_RELATIVE);
+ injectMotionEvent(event);
+ }
+
+ private static void verifyRelativeMouseEvent(InOrder inOrder,
+ View.OnCapturedPointerListener listener, View view, int action, int x, int y) {
+ inOrder.verify(listener, times(1)).onCapturedPointer(
+ eq(view), argThat(new PositionMatcher(action, x, y)));
+ }
+
+ private void verifyHoverDispatch() {
+ View.OnHoverListener listenerOuter = installHoverListener(mOuter);
+ View.OnHoverListener listenerInner = installHoverListener(mInner);
+ View.OnHoverListener listenerTarget = installHoverListener(mTarget);
+ View.OnHoverListener listenerTarget2 = installHoverListener(mTarget2);
+
+ injectMotionEvent(obtainMouseEvent(MotionEvent.ACTION_HOVER_MOVE, mInner, 0, 0));
+ injectMotionEvent(obtainMouseEvent(MotionEvent.ACTION_HOVER_MOVE, mTarget, 0, 0));
+ injectMotionEvent(obtainMouseEvent(MotionEvent.ACTION_HOVER_MOVE, mTarget2, 0, 0));
+
+ clearHoverListener(mOuter);
+ clearHoverListener(mInner);
+ clearHoverListener(mTarget);
+ clearHoverListener(mTarget2);
+
+ verifyEnterMoveExit(listenerInner, mInner, 2);
+ verifyEnterMoveExit(listenerTarget, mTarget, 2);
+ verifyEnterMove(listenerTarget2, mTarget2, 1);
+
+ verifyNoMoreInteractions(listenerOuter);
+ verifyNoMoreInteractions(listenerInner);
+ verifyNoMoreInteractions(listenerTarget);
+ verifyNoMoreInteractions(listenerTarget2);
+ }
+
+ private void assertPointerCapture(boolean enabled) {
+ assertEquals(enabled, mOuter.hasPointerCapture());
+ assertEquals(enabled, mInner.hasPointerCapture());
+ assertEquals(enabled, mTarget.hasPointerCapture());
+ assertEquals(enabled, mTarget2.hasPointerCapture());
+ assertEquals(enabled, mActivity.hasPointerCapture());
+ }
+
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+
+ mOuter = mActivity.findViewById(R.id.outer);
+ mInner = mActivity.findViewById(R.id.inner);
+ mTarget = mActivity.findViewById(R.id.target);
+ mTarget2 = mActivity.findViewById(R.id.target2);
+
+ PollingCheck.waitFor(TIMEOUT_DELTA, mActivity::hasWindowFocus);
+ }
+
+ @Test
+ public void testRequestAndReleaseWorkOnAnyView() throws Throwable {
+ requestCaptureSync(mOuter);
+ assertPointerCapture(true);
+
+ releaseCaptureSync(mOuter);
+ assertPointerCapture(false);
+
+ requestCaptureSync(mInner);
+ assertPointerCapture(true);
+
+ releaseCaptureSync(mTarget);
+ assertPointerCapture(false);
+ }
+
+ @Test
+ public void testWindowFocusChangeEndsCapture() throws Throwable {
+ requestCaptureSync();
+ assertPointerCapture(true);
+
+ // Show a context menu on a widget.
+ mActivity.registerForContextMenu(mTarget);
+ mActivityRule.runOnUiThread(() -> mTarget.showContextMenu(0, 0));
+ PollingCheck.waitFor(TIMEOUT_DELTA, () -> !mOuter.hasWindowFocus());
+ assertPointerCapture(false);
+
+ mInstrumentation.sendCharacterSync(KeyEvent.KEYCODE_BACK);
+ PollingCheck.waitFor(TIMEOUT_DELTA, () -> mOuter.hasWindowFocus());
+ assertFalse(mTarget.hasPointerCapture());
+ assertFalse(mActivity.hasPointerCapture());
+ }
+
+ @Test
+ public void testActivityFocusChangeEndsCapture() throws Throwable {
+ requestCaptureSync();
+ assertPointerCapture(true);
+
+ // Launch another activity.
+ CtsActivity activity = mCtsActivityRule.launchActivity(null);
+ PollingCheck.waitFor(TIMEOUT_DELTA, () -> !mActivity.hasWindowFocus());
+ assertPointerCapture(false);
+
+ activity.finish();
+ PollingCheck.waitFor(TIMEOUT_DELTA, () -> mActivity.hasWindowFocus());
+ assertPointerCapture(false);
+ }
+
+ @Test
+ public void testEventDispatch() throws Throwable {
+ verifyHoverDispatch();
+
+ View.OnCapturedPointerListener listenerInner = installCapturedPointerListener(mInner);
+ View.OnCapturedPointerListener listenerTarget = installCapturedPointerListener(mTarget);
+ View.OnCapturedPointerListener listenerTarget2 = installCapturedPointerListener(mTarget2);
+ View.OnHoverListener hoverListenerTarget2 = installHoverListener(mTarget2);
+
+ requestCaptureSync();
+
+ requestFocusSync(mInner);
+ injectRelativeMouseEvent(MotionEvent.ACTION_MOVE, 1, 2);
+ injectRelativeMouseEvent(MotionEvent.ACTION_DOWN, 1, 2);
+ injectRelativeMouseEvent(MotionEvent.ACTION_MOVE, 3, 4);
+ injectRelativeMouseEvent(MotionEvent.ACTION_UP, 3, 4);
+ injectRelativeMouseEvent(MotionEvent.ACTION_MOVE, 1, 2);
+
+ requestFocusSync(mTarget);
+ injectRelativeMouseEvent(MotionEvent.ACTION_MOVE, 5, 6);
+
+ requestFocusSync(mTarget2);
+ injectRelativeMouseEvent(MotionEvent.ACTION_MOVE, 7, 8);
+
+ requestFocusSync(mInner);
+ injectRelativeMouseEvent(MotionEvent.ACTION_MOVE, 9, 10);
+
+ releaseCaptureSync();
+
+ injectRelativeMouseEvent(MotionEvent.ACTION_MOVE, 11, 12); // Should be ignored.
+
+ clearCapturedPointerListener(mInner);
+ clearCapturedPointerListener(mTarget);
+ clearCapturedPointerListener(mTarget2);
+ clearHoverListener(mTarget2);
+
+ InOrder inOrder = inOrder(
+ listenerInner, listenerTarget, listenerTarget2, hoverListenerTarget2);
+
+ // mTarget2 is left hovered after the call to verifyHoverDispatch.
+ inOrder.verify(hoverListenerTarget2, times(1)).onHover(
+ eq(mTarget2), argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_EXIT)));
+
+ verifyRelativeMouseEvent(inOrder, listenerInner, mInner, MotionEvent.ACTION_MOVE, 1, 2);
+ verifyRelativeMouseEvent(inOrder, listenerInner, mInner, MotionEvent.ACTION_DOWN, 1, 2);
+ verifyRelativeMouseEvent(inOrder, listenerInner, mInner, MotionEvent.ACTION_MOVE, 3, 4);
+ verifyRelativeMouseEvent(inOrder, listenerInner, mInner, MotionEvent.ACTION_UP, 3, 4);
+ verifyRelativeMouseEvent(inOrder, listenerInner, mInner, MotionEvent.ACTION_MOVE, 1, 2);
+
+ verifyRelativeMouseEvent(inOrder, listenerTarget, mTarget, MotionEvent.ACTION_MOVE, 5, 6);
+ verifyRelativeMouseEvent(inOrder, listenerTarget2, mTarget2, MotionEvent.ACTION_MOVE, 7, 8);
+ verifyRelativeMouseEvent(inOrder, listenerInner, mInner, MotionEvent.ACTION_MOVE, 9, 10);
+
+ inOrder.verifyNoMoreInteractions();
+
+ // Check the regular dispatch again.
+ verifyHoverDispatch();
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
index c6e7d1f..dd83c16 100644
--- a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
+++ b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
@@ -29,6 +29,7 @@
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
+import android.view.ViewGroup;
import org.junit.Rule;
import org.junit.Test;
@@ -214,4 +215,43 @@
assertNull(v3.findFocus());
assertNull(v4.findFocus());
}
+
+ @UiThreadTest
+ @Test
+ public void testFocusAuto() {
+ Activity activity = mActivityRule.getActivity();
+
+ activity.getLayoutInflater().inflate(R.layout.focus_handling_auto_layout,
+ (ViewGroup) activity.findViewById(R.id.auto_test_area));
+
+ View def = activity.findViewById(R.id.focusabledefault);
+ View auto = activity.findViewById(R.id.focusableauto);
+ View click = activity.findViewById(R.id.onlyclickable);
+ View clickNotFocus = activity.findViewById(R.id.clickablenotfocusable);
+
+ assertEquals(View.FOCUSABLE_AUTO, auto.getFocusable());
+ assertFalse(auto.isFocusable());
+ assertFalse(def.isFocusable());
+ assertTrue(click.isFocusable());
+ assertFalse(clickNotFocus.isFocusable());
+
+ View test = new View(activity);
+ assertFalse(test.isFocusable());
+ test.setClickable(true);
+ assertTrue(test.isFocusable());
+ test.setFocusable(View.NOT_FOCUSABLE);
+ assertFalse(test.isFocusable());
+ test.setFocusable(View.FOCUSABLE_AUTO);
+ assertTrue(test.isFocusable());
+ test.setClickable(false);
+ assertFalse(test.isFocusable());
+
+ // Make sure setFocusable(boolean) unsets FOCUSABLE_AUTO.
+ auto.setFocusable(true);
+ assertSame(View.FOCUSABLE, auto.getFocusable());
+ auto.setFocusable(View.FOCUSABLE_AUTO);
+ assertSame(View.FOCUSABLE_AUTO, auto.getFocusable());
+ auto.setFocusable(false);
+ assertSame(View.NOT_FOCUSABLE, auto.getFocusable());
+ }
}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
new file mode 100644
index 0000000..681b9bf
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import static org.junit.Assert.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassificationResult;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLanguage;
+import android.view.textclassifier.TextSelection;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassificationManagerTest {
+
+ private TextClassificationManager mTcm;
+ private TextClassifier mClassifier;
+
+ @Before
+ public void setup() {
+ mTcm = InstrumentationRegistry.getTargetContext()
+ .getSystemService(TextClassificationManager.class);
+ mClassifier = mTcm.getDefaultTextClassifier();
+ }
+
+ @Test
+ public void testSmartSelection() {
+ String text = "Contact me at droid@email.com";
+ String selected = "droid";
+ String suggested = "droid@email.com";
+ int startIndex = text.indexOf(selected);
+ int endIndex = startIndex + selected.length();
+ int smartStartIndex = text.indexOf(suggested);
+ int smartEndIndex = smartStartIndex + suggested.length();
+
+ assertThat(mClassifier.suggestSelection(text, startIndex, endIndex),
+ isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
+ }
+
+ @Test
+ public void testTextClassificationResult() {
+ String text = "Contact me at droid@email.com";
+ String classifiedText = "droid@email.com";
+ int startIndex = text.indexOf(classifiedText);
+ assertThat(mClassifier.getTextClassificationResult(text, startIndex, text.length()),
+ isTextClassificationResult(classifiedText, TextClassifier.TYPE_EMAIL));
+ }
+
+ @Test
+ public void testLanguageDetection() {
+ String text = "This is english text";
+ assertThat(mTcm.detectLanguages(text), isDetectedLanguage("en"));
+
+ text = "Das ist deutscher text";
+ assertThat(mTcm.detectLanguages(text), isDetectedLanguage("de"));
+
+ text = "これは日本語テキストです";
+ assertThat(mTcm.detectLanguages(text), isDetectedLanguage("ja"));
+ }
+
+ private static Matcher<TextSelection> isTextSelection(
+ final int startIndex, final int endIndex, final String type) {
+ return new BaseMatcher<TextSelection>() {
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof TextSelection) {
+ TextSelection selection = (TextSelection) o;
+ return startIndex == selection.getSelectionStartIndex()
+ && endIndex == selection.getSelectionEndIndex()
+ && selection.getEntityCount() > 0
+ && type.equals(selection.getEntity(0));
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(
+ String.format("%d, %d, %s=1.0", startIndex, endIndex, type));
+ }
+ };
+ }
+
+ private static Matcher<TextClassificationResult> isTextClassificationResult(
+ final String text, final String type) {
+ return new BaseMatcher<TextClassificationResult>() {
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof TextClassificationResult) {
+ TextClassificationResult result = (TextClassificationResult) o;
+ return text.equals(result.getText())
+ && result.getEntityCount() > 0
+ && type.equals(result.getEntity(0));
+ // TODO: Include other properties.
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("text=").appendValue(text)
+ .appendText(", type=").appendValue(type);
+ }
+ };
+ }
+
+ private static Matcher<List<TextLanguage>> isDetectedLanguage(final String language) {
+ return new BaseMatcher<List<TextLanguage>>() {
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof List) {
+ List languages = (List) o;
+ if (!languages.isEmpty()) {
+ Object o1 = languages.get(0);
+ if (o1 instanceof TextLanguage) {
+ TextLanguage lang = (TextLanguage) o1;
+ return lang.getLanguageCount() > 0
+ && new Locale(language).getLanguage()
+ .equals(lang.getLanguage(0).getLanguage());
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(String.format("%s=1.0", language));
+ }
+ };
+ }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 21438d5..46755c0 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -131,6 +131,9 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextSelection;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@@ -195,6 +198,8 @@
mTextView.setKeyListener(QwertyKeyListener.getInstance(false, Capitalize.NONE));
mTextView.setText("", BufferType.EDITABLE);
mTextView.requestFocus();
+ // Disable smart selection
+ mTextView.setTextClassifier(TextClassifier.NO_OP);
}
/**
@@ -5803,6 +5808,16 @@
// TODO(Bug: 22033189): Tests the set callback is actually used.
}
+ @UiThreadTest
+ @Test
+ public void testRespectsViewFocusability() {
+ TextView v = (TextView) mActivity.findViewById(R.id.textview_singleLine);
+ assertFalse(v.isFocusable());
+ // TextView used to set focusable to true or false verbatim which would break the following.
+ v.setClickable(true);
+ assertTrue(v.isFocusable());
+ }
+
@Test
public void testTextShadows() throws Throwable {
final TextView textViewWithConfiguredShadow =
@@ -6752,6 +6767,32 @@
assertNotEquals(customTextSize, mTextView.getTextSize(), 0f);
}
+ @Test
+ public void testSmartSelection() throws Throwable {
+ mTextView = findTextView(R.id.textview_text);
+ String text = "The president-elect, Filip, is coming to town tomorrow.";
+ int startIndex = text.indexOf("president");
+ int endIndex = startIndex + "president".length();
+ initTextViewForTypingOnUiThread();
+ TextClassificationManager tcm = mActivity.getSystemService(TextClassificationManager.class);
+ mActivityRule.runOnUiThread(() -> {
+ mTextView.setTextIsSelectable(true);
+ mTextView.setText(text, BufferType.EDITABLE);
+ mTextView.setTextClassifier(tcm.getDefaultTextClassifier());
+ });
+ mInstrumentation.waitForIdleSync();
+
+ Point offset = getCenterPositionOfTextAt(mTextView, startIndex, endIndex);
+ CtsTouchUtils.emulateLongPressOnView(mInstrumentation, mTextView, offset.x, offset.y);
+ PollingCheck.waitFor(mTextView::hasSelection);
+
+ TextSelection selection = tcm.getDefaultTextClassifier()
+ .suggestSelection(text, startIndex, endIndex);
+ assertEquals(selection.getSelectionStartIndex(), mTextView.getSelectionStart());
+ assertEquals(selection.getSelectionEndIndex(), mTextView.getSelectionEnd());
+ // TODO: Test the floating toolbar content.
+ }
+
/**
* Some TextView attributes require non-fixed width and/or layout height. This function removes
* all other existing views from the layout leaving only one auto-size TextView (for exercising
@@ -6977,6 +7018,25 @@
mInstrumentation.waitForIdleSync();
}
+ /**
+ * Returns the x, y coordinates of text at a specified indices relative to the position of the
+ * TextView.
+ *
+ * @param textView
+ * @param startIndex start index of the text in the textView
+ * @param endIndex end index of the text in the textView
+ */
+ private static Point getCenterPositionOfTextAt(
+ TextView textView, int startIndex, int endIndex) {
+ int xStart = (int) textView.getLayout().getPrimaryHorizontal(startIndex, true, true);
+ int xEnd = (int) textView.getLayout().getPrimaryHorizontal(endIndex, true, true);
+ int line = textView.getLayout().getLineForOffset(endIndex);
+ int yTop = textView.getLayout().getLineTop(line);
+ int yBottom = textView.getLayout().getLineBottom(line);
+
+ return new Point((xStart + xEnd) / 2 /* x */, (yTop + yBottom) / 2 /* y */);
+ }
+
private static abstract class TestSelectedRunnable implements Runnable {
private TextView mTextView;
private boolean mIsSelected1;
diff --git a/tests/tests/wrap/Android.mk b/tests/tests/wrap/Android.mk
new file mode 100644
index 0000000..ba3fe08
--- /dev/null
+++ b/tests/tests/wrap/Android.mk
@@ -0,0 +1,80 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MULTILIB := both
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util android-support-test
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_SDK_VERSION := current
+
+LOCAL_PREBUILT_JNI_LIBS_arm := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_arm64 := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_mips := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_mips64 := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_x86 := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_x86_64 := wrap.sh
+
+LOCAL_PACKAGE_NAME := CtsWrapWrapDebugTestCases
+LOCAL_MANIFEST_FILE := wrap_debug/AndroidManifest.xml
+
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_MULTILIB := both
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util android-support-test
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_SDK_VERSION := current
+
+LOCAL_PREBUILT_JNI_LIBS_arm := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_arm64 := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_mips := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_mips64 := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_x86 := wrap.sh
+LOCAL_PREBUILT_JNI_LIBS_x86_64 := wrap.sh
+
+LOCAL_PACKAGE_NAME := CtsWrapWrapNoDebugTestCases
+LOCAL_MANIFEST_FILE := wrap_nodebug/AndroidManifest.xml
+
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_MULTILIB := both
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util android-support-test
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsWrapNoWrapTestCases
+LOCAL_MANIFEST_FILE := nowrap/AndroidManifest.xml
+
+include $(BUILD_PACKAGE)
diff --git a/tests/tests/wrap/AndroidTest.xml b/tests/tests/wrap/AndroidTest.xml
new file mode 100644
index 0000000..505eccb
--- /dev/null
+++ b/tests/tests/wrap/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Wrap test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsWrapWrapDebugTestCases.apk" />
+ <option name="test-file-name" value="CtsWrapWrapNoDebugTestCases.apk" />
+ <option name="test-file-name" value="CtsWrapNoWrapTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.wrap.cts" />
+ </test>
+</configuration>
diff --git a/tests/tests/wrap/nowrap/AndroidManifest.xml b/tests/tests/wrap/nowrap/AndroidManifest.xml
new file mode 100644
index 0000000..6145390
--- /dev/null
+++ b/tests/tests/wrap/nowrap/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.wrap.nowrap.cts">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ <meta-data android:name="android.wrap.cts.expext_env" android:value="false" />
+ <activity android:name="android.wrap.WrapActivity" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for wrap.sh"
+ android:targetPackage="android.wrap.nowrap.cts" >
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/wrap/src/android/wrap/WrapActivity.java b/tests/tests/wrap/src/android/wrap/WrapActivity.java
new file mode 100644
index 0000000..578241a
--- /dev/null
+++ b/tests/tests/wrap/src/android/wrap/WrapActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.wrap;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.lang.Override;
+
+/**
+ * A simple no-op activity.
+ */
+public class WrapActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ }
+
+}
diff --git a/tests/tests/wrap/src/android/wrap/cts/WrapTest.java b/tests/tests/wrap/src/android/wrap/cts/WrapTest.java
new file mode 100644
index 0000000..8b73ad5
--- /dev/null
+++ b/tests/tests/wrap/src/android/wrap/cts/WrapTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.wrap.cts;
+
+import android.content.pm.PackageManager;
+import android.wrap.WrapActivity;
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+ * A simple compatibility test which tests the SharedPreferences API.
+ *
+ * This test uses {@link android.test.ActivityInstrumentationTestCase2} to instrument the
+ * {@link android.WrapActivity}.
+ */
+public class WrapTest extends ActivityInstrumentationTestCase2<WrapActivity> {
+
+ /**
+ * A reference to the activity whose shared preferences are being tested.
+ */
+ private WrapActivity mActivity;
+
+ public WrapTest() {
+ super(WrapActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Start the activity.
+ mActivity = getActivity();
+ // Wait for the UI Thread to become idle.
+ getInstrumentation().waitForIdleSync();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Nothing to do here.
+ super.tearDown();
+ }
+
+ /**
+ * Tests the environment for a property.
+ *
+ * This reads the activity meta-data to figure out whether the property is expected, then
+ * check whether the property exists.
+ *
+ * @throws Exception
+ */
+ public void testWrapProperty() throws Exception {
+ boolean expectEnv = mActivity.getPackageManager().getApplicationInfo(
+ mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData.getBoolean(
+ "android.wrap.cts.expext_env");
+
+ String wrapEnvValue = System.getenv("WRAP_PROPERTY");
+ boolean wrapExists = wrapEnvValue != null;
+
+ assertEquals("Unexpected wrap property state", expectEnv, wrapExists);
+
+ if (wrapExists) {
+ assertEquals("Unexpected wrap property value", wrapEnvValue, "test");
+ }
+ }
+}
diff --git a/tests/tests/wrap/wrap.sh b/tests/tests/wrap/wrap.sh
new file mode 100755
index 0000000..58e5f2a
--- /dev/null
+++ b/tests/tests/wrap/wrap.sh
@@ -0,0 +1,15 @@
+#!/system/bin/sh
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+WRAP_PROPERTY=test $@
diff --git a/tests/tests/wrap/wrap_debug/AndroidManifest.xml b/tests/tests/wrap/wrap_debug/AndroidManifest.xml
new file mode 100644
index 0000000..f371108
--- /dev/null
+++ b/tests/tests/wrap/wrap_debug/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.wrap.wrap_debug.cts">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ <meta-data android:name="android.wrap.cts.expext_env" android:value="true" />
+ <activity android:name="android.wrap.WrapActivity" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for wrap.sh"
+ android:targetPackage="android.wrap.wrap_debug.cts" >
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml b/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml
new file mode 100644
index 0000000..127e6a8
--- /dev/null
+++ b/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.wrap.wrap_nodebug.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <meta-data android:name="android.wrap.cts.expext_env" android:value="false" />
+ <activity android:name="android.wrap.WrapActivity" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for wrap.sh"
+ android:targetPackage="android.wrap.wrap_nodebug.cts" >
+ </instrumentation>
+</manifest>
+
diff --git a/tools/cts-tradefed/res/config/cts-global-presubmit.xml b/tools/cts-tradefed/res/config/cts-global-presubmit.xml
new file mode 100644
index 0000000..5a858e7
--- /dev/null
+++ b/tools/cts-tradefed/res/config/cts-global-presubmit.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs CTS global presubmit test cases">
+
+ <include name="cts-automated" />
+
+ <option name="plan" value="cts" />
+
+ <!-- Include modules with presubmit test cases, repeat for each applicable module -->
+ <!--option name="compatibility:include-filter" value="<CTS module name goes here>" /-->
+
+ <!-- Only run tests with @GlobalPresubmit annotation. -->
+ <option name="compatibility:test-arg" value="com.android.compatibility.common.tradefed.testtype.JarHostTest:include-annotation:android.platform.test.annotations.GlobalPresubmit" />
+ <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:include-annotation:android.platform.test.annotations.GlobalPresubmit" />
+ <option name="compatibility:test-arg" value="com.android.tradefed.testtype.HostTest:include-annotation:android.platform.test.annotations.GlobalPresubmit" />
+
+</configuration>
diff --git a/tools/cts-tradefed/res/config/cts-system-checkers.xml b/tools/cts-tradefed/res/config/cts-system-checkers.xml
index ac71bbc..c5225ae 100644
--- a/tools/cts-tradefed/res/config/cts-system-checkers.xml
+++ b/tools/cts-tradefed/res/config/cts-system-checkers.xml
@@ -15,4 +15,5 @@
-->
<configuration description="CTS system checker configs">
<system_checker class="com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker" />
+ <system_checker class="com.android.tradefed.suite.checker.KeyguardStatusChecker" />
</configuration>