Merge "Make NativeHardware CTS tests depend on current SDK."
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index d734b2a..928d9afd 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2924,6 +2924,18 @@
7) Press the Reset button.
</string>
<string name="enterprise_privacy_set_default_apps">Set Default Apps</string>
+ <string name="enterprise_privacy_default_ime">Default keyboard</string>
+ <string name="enterprise_privacy_default_ime_info">
+ Please do the following:\n
+ 1) Press the Open Settings button.\n
+ 2) In the screen that opens, verify that you are not told the default keyboard has been set.\n
+ 3) Use the Back button to return to this page.\n
+ 4) Press the Set Keyboard button to set the default keyboard.\n
+ 5) Repeat steps (1) through (3), verifying that in step (2), you are told now that the default keyboard has been set to CTS Verifier.\n
+ 6) Press the Finish button to clear the default keyboard.
+ </string>
+ <string name="enterprise_privacy_set_keyboard">Set Keyboard</string>
+ <string name="enterprise_privacy_finish">Finish</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
@@ -2935,7 +2947,6 @@
6) Press the Finish button to clear the always-on VPN.
</string>
<string name="enterprise_privacy_set_always_on_vpn">Set VPN</string>
- <string name="enterprise_privacy_finish">Finish</string>
<string name="enterprise_privacy_comp_always_on_vpn">Always-on VPN (managed profile)</string>
<string name="enterprise_privacy_comp_always_on_vpn_info">
Please do the following:\n
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 466436f..baec2a3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -33,6 +33,7 @@
import android.os.UserManager;
import android.provider.ContactsContract;
import android.provider.MediaStore;
+import android.provider.Settings;
import android.util.Log;
import com.android.cts.verifier.R;
@@ -95,6 +96,8 @@
"set-maximum-password-attempts";
public static final String COMMAND_CLEAR_MAXIMUM_PASSWORD_ATTEMPTS =
"clear-maximum-password-attempts";
+ public static final String COMMAND_SET_DEFAULT_IME = "set-default-ime";
+ public static final String COMMAND_CLEAR_DEFAULT_IME = "clear-default-ime";
public static final String EXTRA_USER_RESTRICTION =
"com.android.cts.verifier.managedprovisioning.extra.USER_RESTRICTION";
@@ -393,6 +396,19 @@
return;
}
mDpm.setMaximumFailedPasswordsForWipe(mAdmin, 0);
+ } break;
+ case COMMAND_SET_DEFAULT_IME: {
+ if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+ return;
+ }
+ mDpm.setSecureSetting(mAdmin, Settings.Secure.DEFAULT_INPUT_METHOD,
+ getPackageName());
+ } break;
+ case COMMAND_CLEAR_DEFAULT_IME: {
+ if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+ return;
+ }
+ mDpm.setSecureSetting(mAdmin, Settings.Secure.DEFAULT_INPUT_METHOD, null);
}
}
} catch (Exception e) {
@@ -459,6 +475,7 @@
mDpm.setAlwaysOnVpnPackage(mAdmin, null, false);
mDpm.setRecommendedGlobalProxy(mAdmin, null);
mDpm.setMaximumFailedPasswordsForWipe(mAdmin, 0);
+ mDpm.setSecureSetting(mAdmin, Settings.Secure.DEFAULT_INPUT_METHOD, null);
uninstallHelperPackage();
removeManagedProfile();
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 1844f20..165bcf7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
@@ -54,6 +54,8 @@
= "ENTERPRISE_PRIVACY_CAMERA_ACCESS";
private static final String ENTERPRISE_PRIVACY_DEFAULT_APPS
= "ENTERPRISE_PRIVACY_DEFAULT_APPS";
+ private static final String ENTERPRISE_PRIVACY_DEFAULT_IME
+ = "ENTERPRISE_PRIVACY_DEFAULT_IME";
private static final String ENTERPRISE_PRIVACY_ALWAYS_ON_VPN
= "ENTERPRISE_PRIVACY_ALWAYS_ON_VPN";
private static final String ENTERPRISE_PRIVACY_COMP_ALWAYS_ON_VPN
@@ -180,6 +182,18 @@
new ButtonInfo(R.string.enterprise_privacy_set_default_apps,
buildCommandIntent(CommandReceiverActivity
.COMMAND_ADD_PERSISTENT_PREFERRED_ACTIVITIES))}));
+ adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_DEFAULT_IME,
+ R.string.enterprise_privacy_default_ime,
+ R.string.enterprise_privacy_default_ime_info,
+ new ButtonInfo[] {
+ new ButtonInfo(R.string.enterprise_privacy_open_settings,
+ new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
+ new ButtonInfo(R.string.enterprise_privacy_set_keyboard,
+ buildCommandIntent(CommandReceiverActivity
+ .COMMAND_SET_DEFAULT_IME)),
+ new ButtonInfo(R.string.enterprise_privacy_finish,
+ buildCommandIntent(CommandReceiverActivity
+ .COMMAND_CLEAR_DEFAULT_IME))}));
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/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index aba4833..335abf4 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -66,6 +66,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Collect test results for an entire invocation and output test results to disk.
@@ -111,6 +113,7 @@
private String mReferenceUrl;
private ILogSaver mLogSaver;
private int invocationEndedCount = 0;
+ private CountDownLatch mFinalized = null;
private IInvocationResult mResult = new InvocationResult();
private IModuleResult mCurrentModuleResult;
@@ -145,6 +148,7 @@
*/
public ResultReporter() {
this(null);
+ mFinalized = new CountDownLatch(1);
}
/**
@@ -452,6 +456,7 @@
return;
}
finalizeResults(elapsedTime);
+ mFinalized.countDown();
}
}
@@ -808,4 +813,12 @@
public IInvocationResult getResult() {
return mResult;
}
+
+ /**
+ * Returns true if the reporter is finalized before the end of the timeout. False otherwise.
+ */
+ @VisibleForTesting
+ public boolean waitForFinalized(long timeout, TimeUnit unit) throws InterruptedException {
+ return mFinalized.await(timeout, unit);
+ }
}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
index 3321443..c8a8905 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
@@ -16,6 +16,7 @@
package com.android.compatibility.common.tradefed.presubmit;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.compatibility.SuiteInfo;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -38,7 +39,6 @@
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.RunUtil;
import org.easymock.EasyMock;
import org.junit.After;
@@ -56,6 +56,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* Integration tests between {@link CompatibilityTest} and {@link ResultReporter} to ensure proper
@@ -483,7 +484,8 @@
thread.join(1000);
}
// Allow some time for ResultReport to finalize the results coming from the threads.
- RunUtil.getDefault().sleep(200);
+ boolean finalized = mReporter.waitForFinalized(2000, TimeUnit.MILLISECONDS);
+ assertTrue(finalized);
EasyMock.verify(mMockDevice, mMockBuildInfo);
// Check aggregated results to make sure it's consistent.
IInvocationResult result = mReporter.getResult();
@@ -533,7 +535,8 @@
thread.join(1000);
}
// Allow some time for ResultReport to finalize the results coming from the threads.
- RunUtil.getDefault().sleep(200);
+ boolean finalized = mReporter.waitForFinalized(2000, TimeUnit.MILLISECONDS);
+ assertTrue(finalized);
EasyMock.verify(mMockDevice, mMockBuildInfo);
// Check aggregated results to make sure it's consistent.
IInvocationResult result = mReporter.getResult();
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
index 034da99..e5a483c 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
@@ -89,7 +89,7 @@
private String installPackage(String apk, boolean replace, boolean instant) throws Exception {
return getDevice().installPackage(mBuildHelper.getTestFile(apk), replace,
- instant ? "--ephemeral" : "");
+ instant ? "--instant" : "--full");
}
private String uninstallPackage(String packageName) throws DeviceNotAvailableException {
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
index 63b2f21..15197a0 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
@@ -19,7 +19,6 @@
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -32,8 +31,6 @@
import android.os.UserManager;
import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiSelector;
import android.test.InstrumentationTestCase;
import android.text.format.DateUtils;
import android.util.Log;
@@ -43,9 +40,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-// for another encryption ui setting package
-import android.content.pm.ResolveInfo;
-
public class EncryptionAppTest extends InstrumentationTestCase {
private static final String TAG = "EncryptionAppTest";
@@ -65,9 +59,6 @@
private UiDevice mDevice;
private AwareActivity mActivity;
- // for another encryption ui setting package
- private String mEncryptionSettingPackage = "com.android.settings";
-
@Override
public void setUp() throws Exception {
super.setUp();
@@ -76,13 +67,6 @@
mDe = mCe.createDeviceProtectedStorageContext();
mPm = mCe.getPackageManager();
- final Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- ResolveInfo rInfo = mPm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
- if (rInfo != null) {
- mEncryptionSettingPackage = rInfo.activityInfo.packageName;
- }
-
mDevice = UiDevice.getInstance(getInstrumentation());
assertNotNull(mDevice);
}
@@ -108,44 +92,7 @@
mDevice.waitForIdle();
// Set a PIN for this user
- final Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- mActivity.startActivity(intent);
- mDevice.waitForIdle();
-
- // Pick PIN from the option list
- UiObject view = new UiObject(new UiSelector()
- .resourceId(mEncryptionSettingPackage + ":id/lock_pin"));
- assertTrue("lock_pin", view.waitForExists(TIMEOUT));
- view.click();
- mDevice.waitForIdle();
-
- // Ignore any interstitial options
- view = new UiObject(new UiSelector()
- .resourceId(mEncryptionSettingPackage + ":id/encrypt_dont_require_password"));
- if (view.waitForExists(TIMEOUT)) {
- view.click();
- mDevice.waitForIdle();
- }
-
- // Yes, we really want to
- view = new UiObject(new UiSelector()
- .resourceId(mEncryptionSettingPackage + ":id/next_button"));
- if (view.waitForExists(TIMEOUT)) {
- view.click();
- mDevice.waitForIdle();
- }
-
- // Set our PIN
- view = new UiObject(new UiSelector()
- .resourceId(mEncryptionSettingPackage + ":id/password_entry"));
- assertTrue("password_entry", view.waitForExists(TIMEOUT));
-
- // Enter it twice to confirm
- enterTestPin();
- enterTestPin();
-
- mDevice.pressBack();
+ mDevice.executeShellCommand("locksettings set-pin 12345");
}
public void testTearDown() throws Exception {
@@ -157,48 +104,7 @@
mDevice.waitForIdle();
// Clear PIN for this user
- final Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- mActivity.startActivity(intent);
- mDevice.waitForIdle();
-
- // Enter current PIN
- UiObject view = new UiObject(new UiSelector()
- .resourceId(mEncryptionSettingPackage + ":id/password_entry"));
- if (!view.waitForExists(TIMEOUT)) {
- // Odd, maybe there is a crash dialog showing; try dismissing it
- mDevice.pressBack();
- mDevice.waitForIdle();
-
- assertTrue("password_entry", view.waitForExists(TIMEOUT));
- }
-
- enterTestPin();
-
- // Set back to "none"
- view = new UiObject(new UiSelector()
- .resourceId(mEncryptionSettingPackage + ":id/lock_none"));
- assertTrue("lock_none", view.waitForExists(TIMEOUT));
- view.click();
- mDevice.waitForIdle();
-
- // Yes, we really want "none" if prompted again
- view = new UiObject(new UiSelector()
- .resourceId("com.android.settings:id/lock_none"));
- if (view.waitForExists(TIMEOUT)) {
- view.click();
- mDevice.waitForIdle();
- }
-
- // Yes, we really want to
- view = new UiObject(new UiSelector()
- .resourceId("android:id/button1"));
- if (view.waitForExists(TIMEOUT)) {
- view.click();
- mDevice.waitForIdle();
- }
-
- mDevice.pressBack();
+ mDevice.executeShellCommand("locksettings clear --old 12345");
}
public void doBootCountBefore() throws Exception {
diff --git a/hostsidetests/backup/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
index f9e1d36..f81279c 100644
--- a/hostsidetests/backup/AndroidTest.xml
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -17,6 +17,7 @@
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsBackupDeviceApp.apk" />
+ <option name="test-file-name" value="CtsBackupDeviceApp2.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsBackupHostTestCases.jar" />
diff --git a/hostsidetests/backup/app/Android.mk b/hostsidetests/backup/app/Android.mk
index 2573bd9..7e40c5a 100644
--- a/hostsidetests/backup/app/Android.mk
+++ b/hostsidetests/backup/app/Android.mk
@@ -25,8 +25,6 @@
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
-
LOCAL_SRC_FILES := $(call all-java-files-under, src)
# tag this module as a cts test artifact
diff --git a/hostsidetests/backup/app/AndroidManifest.xml b/hostsidetests/backup/app/AndroidManifest.xml
index e5dd962..e67404c 100644
--- a/hostsidetests/backup/app/AndroidManifest.xml
+++ b/hostsidetests/backup/app/AndroidManifest.xml
@@ -19,7 +19,6 @@
package="android.backup.cts.app">
<application>
- <uses-library android:name="android.test.runner" />
<service
android:name="android.backup.cts.app.DummyLiveWallpaper"
@@ -36,8 +35,4 @@
</application>
- <instrumentation
- android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.backup.cts.app" />
-
</manifest>
diff --git a/hostsidetests/backup/app2/Android.mk b/hostsidetests/backup/app2/Android.mk
new file mode 100644
index 0000000..c88be6d
--- /dev/null
+++ b/hostsidetests/backup/app2/Android.mk
@@ -0,0 +1,39 @@
+# 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
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsBackupDeviceApp2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/app2/AndroidManifest.xml b/hostsidetests/backup/app2/AndroidManifest.xml
new file mode 100644
index 0000000..518f26f
--- /dev/null
+++ b/hostsidetests/backup/app2/AndroidManifest.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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.backup.cts.app2">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.backup.cts.app2" />
+
+</manifest>
diff --git a/hostsidetests/backup/app/src/android/backup/cts/app/WallpaperRestoreTest.java b/hostsidetests/backup/app2/src/android/backup/cts/app2/WallpaperRestoreTest.java
similarity index 98%
rename from hostsidetests/backup/app/src/android/backup/cts/app/WallpaperRestoreTest.java
rename to hostsidetests/backup/app2/src/android/backup/cts/app2/WallpaperRestoreTest.java
index 68b4c1c..3bb294f 100644
--- a/hostsidetests/backup/app/src/android/backup/cts/app/WallpaperRestoreTest.java
+++ b/hostsidetests/backup/app2/src/android/backup/cts/app2/WallpaperRestoreTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.backup.cts.app;
+package android.backup.cts.app2;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
diff --git a/hostsidetests/backup/src/android/backup/cts/WallpaperRestoreHostSideTest.java b/hostsidetests/backup/src/android/backup/cts/WallpaperRestoreHostSideTest.java
index 46d9262..cc2dbbc 100644
--- a/hostsidetests/backup/src/android/backup/cts/WallpaperRestoreHostSideTest.java
+++ b/hostsidetests/backup/src/android/backup/cts/WallpaperRestoreHostSideTest.java
@@ -96,8 +96,8 @@
private void checkDeviceTest(String testName) throws DeviceNotAvailableException {
boolean result =
runDeviceTests(
- "android.backup.cts.app",
- "android.backup.cts.app.WallpaperRestoreTest",
+ "android.backup.cts.app2",
+ "android.backup.cts.app2.WallpaperRestoreTest",
testName);
assertTrue("Device test failed: " + testName, result);
}
diff --git a/hostsidetests/compilation/assets/primary.prof b/hostsidetests/compilation/assets/primary.prof
index cef941e..71c158d 100644
--- a/hostsidetests/compilation/assets/primary.prof
+++ b/hostsidetests/compilation/assets/primary.prof
Binary files differ
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
index 5a6794a..516e32e 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
@@ -24,6 +24,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+LOCAL_JAVA_LIBRARIES = conscrypt
+
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
index cf8a05d..962c5f5 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
@@ -24,6 +24,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+LOCAL_JAVA_LIBRARIES = conscrypt
+
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
index 5e083f7..1167a08 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
@@ -24,6 +24,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+LOCAL_JAVA_LIBRARIES = conscrypt
+
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
index cebdb68..3a6e65a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
@@ -24,9 +24,18 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
import android.security.KeyChainException;
import android.test.MoreAsserts;
+import com.android.org.conscrypt.TrustedCertificateStore;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -170,14 +179,18 @@
super.tearDown();
}
- public void testCaCertsOperations() throws InterruptedException {
- byte[] cert = TEST_CA.getBytes();
+ public void testCaCertsOperations() throws InterruptedException, CertificateException {
+ final byte[] cert = TEST_CA.getBytes();
+ final Certificate caCert = CertificateFactory.getInstance("X.509")
+ .generateCertificate(new ByteArrayInputStream(cert));
+ final TrustedCertificateStore store = new TrustedCertificateStore();
mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE);
assertEquals(CERT_INSTALLER_PACKAGE,
mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT));
// Exercise installCaCert()
+ assertNull(store.getCertificateAlias(caCert));
installCaCert(cert);
assertResult("installCaCert", true);
assertTrue("Certificate is not installed properly", mDpm.hasCaCertInstalled(
@@ -187,12 +200,21 @@
verifyCaCert(cert);
assertResult("getInstalledCaCerts()", true);
+ // Verify that the CA cert was marked as installed by the Device Owner / Profile Owner.
+ final String alias = store.getCertificateAlias(caCert);
+ assertNotNull(alias);
+ verifyOwnerInstalledStatus(alias, true);
+
// Exercise uninstallCaCert()
removeCaCert(cert);
assertResult("uninstallCaCert()", true);
assertFalse("Certificate is not removed properly", mDpm.hasCaCertInstalled(
ADMIN_RECEIVER_COMPONENT, cert));
+ // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
+ // Owner.
+ verifyOwnerInstalledStatus(alias, false);
+
// Clear delegated cert installer.
// Tests after this are expected to fail.
mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, null);
@@ -288,6 +310,13 @@
mContext.sendBroadcast(intent);
}
+ private void verifyOwnerInstalledStatus(String alias, boolean expectOwnerInstalled) {
+ final List<String> ownerInstalledCerts =
+ mDpm.getOwnerInstalledCaCerts(Process.myUserHandle());
+ assertNotNull(ownerInstalledCerts);
+ assertEquals(expectOwnerInstalled, ownerInstalledCerts.contains(alias));
+ }
+
private void assertResult(String testName, Boolean expectSuccess) throws InterruptedException {
assertTrue("Cert installer did not respond in time.",
mAvailableResultSemaphore.tryAcquire(10, TimeUnit.SECONDS));
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
index a2d7a60..4e9abd7 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -24,7 +24,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java
index a5e2768..ff51e9e 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java
@@ -20,17 +20,50 @@
import android.content.ContentResolver;
import android.content.Context;
import android.os.Process;
-import android.os.UserHandle;
import android.provider.Settings;
+import com.android.org.conscrypt.TrustedCertificateStore;
+
+import java.io.ByteArrayInputStream;
import java.lang.reflect.Method;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.List;
public class AdminActionBookkeepingTest extends BaseDeviceOwnerTest {
+ /*
+ * The CA cert below is the content of cacert.pem as generated by:
+ *
+ * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+ */
+ private static final String TEST_CA =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+ "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+ "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+ "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+ "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+ "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+ "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+ "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+ "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+ "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+ "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+ "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+ "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+ "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+ "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+ "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+ "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+ "wQ==\n" +
+ "-----END CERTIFICATE-----";
@Override
protected void tearDown() throws Exception {
mDevicePolicyManager.setSecurityLoggingEnabled(getWho(), false);
mDevicePolicyManager.setNetworkLoggingEnabled(getWho(), false);
+ mDevicePolicyManager.uninstallCaCert(getWho(), TEST_CA.getBytes());
super.tearDown();
}
@@ -134,24 +167,55 @@
}
/**
- * Test: It should be recored whether the Device Owner or the user set the default IME.
+ * Test: It should be recored whether the Device Owner or the user set the current IME.
*/
public void testIsDefaultInputMethodSet() throws Exception {
- final UserHandle userHandle = Process.myUserHandle();
final String setting = Settings.Secure.DEFAULT_INPUT_METHOD;
final ContentResolver resolver = getContext().getContentResolver();
final String ime = Settings.Secure.getString(resolver, setting);
Settings.Secure.putString(resolver, setting, "com.test.1");
Thread.sleep(500);
- assertFalse(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertFalse(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
mDevicePolicyManager.setSecureSetting(getWho(), setting, "com.test.2");
Thread.sleep(500);
- assertTrue(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertTrue(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
Settings.Secure.putString(resolver, setting, ime);
Thread.sleep(500);
- assertFalse(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertFalse(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
+ }
+
+ /**
+ * Test: It should be recored whether the Device Owner or the user installed a CA cert.
+ */
+ public void testGetPolicyInstalledCaCerts() throws Exception {
+ final byte[] rawCert = TEST_CA.getBytes();
+ final Certificate cert = CertificateFactory.getInstance("X.509")
+ .generateCertificate(new ByteArrayInputStream(rawCert));
+ final TrustedCertificateStore store = new TrustedCertificateStore();
+
+ // Install a CA cert.
+ assertNull(store.getCertificateAlias(cert));
+ assertTrue(mDevicePolicyManager.installCaCert(getWho(), rawCert));
+ final String alias = store.getCertificateAlias(cert);
+ assertNotNull(alias);
+
+ // Verify that the CA cert was marked as installed by the Device Owner.
+ verifyOwnerInstalledStatus(alias, true);
+
+ // Uninstall the CA cert.
+ mDevicePolicyManager.uninstallCaCert(getWho(), rawCert);
+
+ // Verify that the CA cert is no longer marked as installed by the Device Owner.
+ verifyOwnerInstalledStatus(alias, false);
+ }
+
+ private void verifyOwnerInstalledStatus(String alias, boolean expectOwnerInstalled) {
+ final List<String> ownerInstalledCerts =
+ mDevicePolicyManager.getOwnerInstalledCaCerts(Process.myUserHandle());
+ assertNotNull(ownerInstalledCerts);
+ assertEquals(expectOwnerInstalled, ownerInstalledCerts.contains(alias));
}
}
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
index 6b83df7..6f9ef5d 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
@@ -24,7 +24,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util ub-uiautomator
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AdminActionBookkeepingTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AdminActionBookkeepingTest.java
index 8bfa080..ba7919d 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AdminActionBookkeepingTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AdminActionBookkeepingTest.java
@@ -22,26 +22,99 @@
import android.os.UserHandle;
import android.provider.Settings;
+import com.android.org.conscrypt.TrustedCertificateStore;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.List;
+
public class AdminActionBookkeepingTest extends BaseProfileOwnerTest {
+ /*
+ * The CA cert below is the content of cacert.pem as generated by:
+ *
+ * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+ */
+ private static final String TEST_CA =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+ "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+ "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+ "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+ "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+ "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+ "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+ "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+ "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+ "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+ "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+ "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+ "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+ "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+ "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+ "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+ "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+ "wQ==\n" +
+ "-----END CERTIFICATE-----";
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDevicePolicyManager.uninstallCaCert(getWho(), TEST_CA.getBytes());
+
+ super.tearDown();
+ }
+
/**
- * Test: It should be recored whether the Profile Owner or the user set the default IME.
+ * Test: It should be recored whether the Profile Owner or the user set the current IME.
*/
public void testIsDefaultInputMethodSet() throws Exception {
- final UserHandle userHandle = Process.myUserHandle();
final String setting = Settings.Secure.DEFAULT_INPUT_METHOD;
final ContentResolver resolver = getContext().getContentResolver();
final String ime = Settings.Secure.getString(resolver, setting);
Settings.Secure.putString(resolver, setting, "com.test.1");
Thread.sleep(500);
- assertFalse(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertFalse(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
mDevicePolicyManager.setSecureSetting(getWho(), setting, "com.test.2");
Thread.sleep(500);
- assertTrue(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertTrue(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
Settings.Secure.putString(resolver, setting, ime);
Thread.sleep(500);
- assertFalse(mDevicePolicyManager.isDefaultInputMethodSetByOwner(userHandle));
+ assertFalse(mDevicePolicyManager.isCurrentInputMethodSetByOwner());
+ }
+
+ /**
+ * Test: It should be recored whether the Profile Owner or the user installed a CA cert.
+ */
+ public void testGetPolicyInstalledCaCerts() throws Exception {
+ final byte[] rawCert = TEST_CA.getBytes();
+ final Certificate cert = CertificateFactory.getInstance("X.509")
+ .generateCertificate(new ByteArrayInputStream(rawCert));
+ final TrustedCertificateStore store = new TrustedCertificateStore();
+
+ // Install a CA cert.
+ assertNull(store.getCertificateAlias(cert));
+ assertTrue(mDevicePolicyManager.installCaCert(getWho(), rawCert));
+ final String alias = store.getCertificateAlias(cert);
+ assertNotNull(alias);
+
+ // Verify that the CA cert was marked as installed by the Profile Owner.
+ verifyOwnerInstalledStatus(alias, true);
+
+ // Uninstall the CA cert.
+ mDevicePolicyManager.uninstallCaCert(getWho(), rawCert);
+
+ // Verify that the CA cert is no longer marked as installed by the Profile Owner.
+ verifyOwnerInstalledStatus(alias, false);
+ }
+
+ private void verifyOwnerInstalledStatus(String alias, boolean expectOwnerInstalled) {
+ final List<String> ownerInstalledCerts =
+ mDevicePolicyManager.getOwnerInstalledCaCerts(Process.myUserHandle());
+ assertNotNull(ownerInstalledCerts);
+ assertEquals(expectOwnerInstalled, ownerInstalledCerts.contains(alias));
}
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index f7ba94f..6a68a59 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -58,6 +58,22 @@
private static final long TIMEOUT_USER_REMOVED_MILLIS = TimeUnit.SECONDS.toMillis(15);
private static final long WAIT_SAMPLE_INTERVAL_MILLIS = 200;
+ /**
+ * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
+ * command output from the device. At any time, if the shell command does not output anything
+ * for a period longer than defined timeout the Tradefed run terminates.
+ */
+ private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20);
+
+ /** instrumentation test runner argument key used for individual test timeout */
+ protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
+
+ /**
+ * Sets timeout (in milliseconds) that will be applied to each test. In the
+ * event of a test timeout it will log the results and proceed with executing the next test.
+ */
+ private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
+
// From the UserInfo class
protected static final int FLAG_PRIMARY = 0x00000001;
protected static final int FLAG_GUEST = 0x00000004;
@@ -283,6 +299,9 @@
RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
pkgName, RUNNER, getDevice().getIDevice());
+ testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ testRunner.addInstrumentationArg(
+ TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
if (testClassName != null && testMethodName != null) {
testRunner.setMethodName(testClassName, testMethodName);
} else if (testClassName != null) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 614343b..ba36fbf 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -398,6 +398,9 @@
/** Tests for the API helper class. */
public void testCurrentApiHelper() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CurrentApiHelperTest",
mProfileUserId);
}
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index f69ceab..efafca2 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -325,7 +325,7 @@
}
private void checkNetwork(String[] parts) {
- assertEquals(18, parts.length);
+ assertEquals(26, parts.length);
long mbRx = assertInteger(parts[4]); // mobileBytesRx
long mbTx = assertInteger(parts[5]); // mobileBytesTx
long wbRx = assertInteger(parts[6]); // wifiBytesRx
@@ -340,6 +340,14 @@
assertInteger(parts[15]); // btBytesTx
assertInteger(parts[16]); // mobileWakeup
assertInteger(parts[17]); // wifiWakeup
+ long mbBgRx = assertInteger(parts[18]); // mobileBytesRx
+ long mbBgTx = assertInteger(parts[19]); // mobileBytesTx
+ long wbBgRx = assertInteger(parts[20]); // wifiBytesRx
+ long wbBgTx = assertInteger(parts[21]); // wifiBytesTx
+ long mpBgRx = assertInteger(parts[22]); // mobilePacketsRx
+ long mpBgTx = assertInteger(parts[23]); // mobilePacketsTx
+ long wpBgRx = assertInteger(parts[24]); // wifiPacketsRx
+ long wpBgTx = assertInteger(parts[25]); // wifiPacketsTx
// Assuming each packet contains some bytes, bytes >= packets >= 0.
assertTrue("mobileBytesRx must be >= mobilePacketsRx", mbRx >= mpRx);
@@ -350,6 +358,15 @@
assertTrue("wifiPacketsRx must be >= 0", wpRx >= 0);
assertTrue("wifiBytesTx must be >= wifiPacketsTx", wbTx >= wpTx);
assertTrue("wifiPacketsTx must be >= 0", wpTx >= 0);
+ // Totals should be greater than or equal to background data numbers
+ assertTrue("mobileBytesRx must be >= mobileBytesBgRx", mbRx >= mbBgRx);
+ assertTrue("mobilePacketsRx must be >= mobilePacketsBgRx", mpRx >= mpBgRx);
+ assertTrue("mobileBytesTx must be >= mobileBytesBgTx", mbTx >= mbBgTx);
+ assertTrue("mobilePacketsTx must be >= mobilePacketsBgTx", mpTx >= mpBgTx);
+ assertTrue("wifiBytesRx must be >= wifiBytesBgRx", wbRx >= wbBgRx);
+ assertTrue("wifiPacketsRx must be >= wifiPacketsBgRx", wpRx >= wpBgRx);
+ assertTrue("wifiBytesTx must be >= wifiBytesBgTx", wbTx >= wbBgTx);
+ assertTrue("wifiPacketsTx must be >= wifiPacketsBgTx", wpTx >= wpBgTx);
}
private void checkUserActivity(String[] parts) {
diff --git a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
index f52e4ce..41370d8 100644
--- a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
+++ b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
@@ -20,8 +20,10 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
- <application>
+ <application android:label="@string/app_name">
<uses-library android:name="android.test.runner" />
<service android:name=".SimpleForegroundService" android:exported="true" />
@@ -32,6 +34,28 @@
<activity android:name=".SimpleActivity" android:label="BatteryStats Test Activity" />
<activity android:name=".BatteryStatsWifiScanTests$WiFiScanActivity" android:label="BatteryStats Wifi scan Test Activity" />
+
+ <service android:name=".BatteryStatsAuthenticator"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.accounts.AccountAuthenticator" />
+ </intent-filter>
+
+ <meta-data android:name="android.accounts.AccountAuthenticator"
+ android:resource="@xml/authenticator" />
+ </service>
+ <service android:name=".BatteryStatsSyncService"
+ android:exported="false" >
+ <intent-filter>
+ <action android:name="android.content.SyncAdapter" />
+ </intent-filter>
+ <meta-data android:name="android.content.SyncAdapter"
+ android:resource="@xml/syncadapter" />
+ </service>
+
+ <provider android:name=".BatteryStatsProvider"
+ android:authorities="com.android.server.cts.device.batterystats.provider" />
+
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/incident/apps/batterystatsapp/res/values/strings.xml b/hostsidetests/incident/apps/batterystatsapp/res/values/strings.xml
new file mode 100644
index 0000000..20783ed
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/res/values/strings.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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">BatteryStats CTS</string>
+</resources>
diff --git a/hostsidetests/incident/apps/batterystatsapp/res/xml/authenticator.xml b/hostsidetests/incident/apps/batterystatsapp/res/xml/authenticator.xml
new file mode 100644
index 0000000..3c5a04c
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/res/xml/authenticator.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accountType="com.android.server.cts.device.batterystats"
+ android:label="@string/app_name" />
diff --git a/hostsidetests/incident/apps/batterystatsapp/res/xml/syncadapter.xml b/hostsidetests/incident/apps/batterystatsapp/res/xml/syncadapter.xml
new file mode 100644
index 0000000..fca349c
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/res/xml/syncadapter.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.
+-->
+
+<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+ android:contentAuthority="com.android.server.cts.device.batterystats.provider"
+ android:accountType="com.android.server.cts.device.batterystats"
+/>
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
new file mode 100644
index 0000000..c49a824
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsAlarmTest {
+ private static final String TAG = "BatteryStatsAlarmTest";
+
+ /**
+ * Set and fire a wakeup alarm 3 times.
+ */
+ @Test
+ public void testAlarms() throws Exception {
+ final int NUM_ALARMS = 3;
+
+ final Context context = InstrumentationRegistry.getContext();
+
+ final Intent intent = new Intent("com.android.server.cts.device.batterystats.ALARM");
+ final IntentFilter inf = new IntentFilter(intent.getAction());
+
+ final CountDownLatch latch = new CountDownLatch(NUM_ALARMS);
+
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Received: " + intent);
+ latch.countDown();
+ }}, inf);
+
+ final AlarmManager alm = context.getSystemService(AlarmManager.class);
+ for (int i = 0; i < NUM_ALARMS; i++) {
+ alm.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + (i + 1) * 1000,
+ PendingIntent.getBroadcast(context, i, intent, 0));
+ }
+ assertTrue("Didn't receive all broadcasts.", latch.await(60 * 1000, TimeUnit.SECONDS));
+ }
+}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAuthenticator.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAuthenticator.java
new file mode 100644
index 0000000..bfc5740
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAuthenticator.java
@@ -0,0 +1,136 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Authenticator for the sync test.
+ */
+public class BatteryStatsAuthenticator extends Service {
+ private static final String TAG = "TestAuthenticator";
+
+ private static final String ACCOUNT_NAME = "BatteryStatsCts";
+ private static final String ACCOUNT_TYPE = "com.android.server.cts.device.batterystats";
+
+ private static Authenticator sInstance;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (sInstance == null) {
+ sInstance = new Authenticator(getApplicationContext());
+
+ }
+ return sInstance.getIBinder();
+ }
+
+ public static Account getTestAccount() {
+ return new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+ }
+
+ /**
+ * Adds the test account, if it doesn't exist yet.
+ */
+ public static void ensureTestAccount(Context context) {
+ final Account account = getTestAccount();
+
+ Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+
+ final AccountManager am = context.getSystemService(AccountManager.class);
+
+ if (!Arrays.asList(am.getAccountsByType(account.type)).contains(account) ){
+ am.addAccountExplicitly(account, "password", new Bundle());
+ }
+ }
+
+ /**
+ * Remove the test account.
+ */
+ public static void removeAllAccounts(Context context) {
+ final AccountManager am = context.getSystemService(AccountManager.class);
+
+ for (Account account : am.getAccountsByType(BatteryStatsAuthenticator.ACCOUNT_TYPE)) {
+ Log.i(TAG, "Removing " + account + "...");
+ am.removeAccountExplicitly(account);
+ Log.i(TAG, "Removed");
+ }
+ }
+
+ public static class Authenticator extends AbstractAccountAuthenticator {
+
+ private final Context mContxet;
+
+ public Authenticator(Context context) {
+ super(context);
+ mContxet = context;
+ }
+
+ @Override
+ public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
+ String authTokenType, String[] requiredFeatures, Bundle options)
+ throws NetworkErrorException {
+ return new Bundle();
+ }
+
+ @Override
+ public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+ return new Bundle();
+ }
+
+ @Override
+ public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
+ String authTokenType, Bundle options) throws NetworkErrorException {
+ return new Bundle();
+ }
+
+ @Override
+ public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
+ Bundle options) throws NetworkErrorException {
+ return new Bundle();
+ }
+
+ @Override
+ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
+ String authTokenType, Bundle options) throws NetworkErrorException {
+ return new Bundle();
+ }
+
+ @Override
+ public String getAuthTokenLabel(String authTokenType) {
+ return "token_label";
+ }
+
+ @Override
+ public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
+ String[] features) throws NetworkErrorException {
+ return new Bundle();
+ }
+ }
+}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsProvider.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsProvider.java
new file mode 100644
index 0000000..c9dcedb
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsProvider.java
@@ -0,0 +1,59 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Provider for the sync test.
+ */
+public class BatteryStatsProvider extends ContentProvider {
+ public static final String AUTHORITY = "com.android.server.cts.device.batterystats.provider";
+
+ @Override
+ public boolean onCreate() {
+ return false;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsSyncAdapter.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsSyncAdapter.java
new file mode 100644
index 0000000..ec1522b
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsSyncAdapter.java
@@ -0,0 +1,135 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+
+import org.junit.Assert;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Sync adapter for the sync test.
+ */
+public class BatteryStatsSyncAdapter extends AbstractThreadedSyncAdapter {
+ private static final String TAG = "BatteryStatsSyncAdapter";
+
+ private static final int TIMEOUT_SECONDS = 60 * 2;
+
+ private static final Object sLock = new Object();
+
+ /**
+ * # of total syncs happened; used to wait until a request sync finishes.
+ */
+ @GuardedBy("sLock")
+ private static int sSyncCount;
+
+ public BatteryStatsSyncAdapter(Context context) {
+ // No need for auto-initialization because we set isSyncable in the test anyway.
+ super(context, /* autoInitialize= */ false);
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority,
+ ContentProviderClient provider, SyncResult syncResult) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+ synchronized (sLock) {
+ sSyncCount++;
+ Log.i(TAG, "onPerformSync: count -> " + sSyncCount);
+ sLock.notifyAll();
+ }
+ }
+
+ /**
+ * Returns the current sync count.
+ */
+ private static int getSyncCount() {
+ synchronized (sLock) {
+ return sSyncCount;
+ }
+ }
+
+ /**
+ * Wait until the sync count reaches the given value.
+ */
+ private static void waitUntilSyncCount(int expectCount) throws Exception {
+ final long timeout = SystemClock.elapsedRealtime() + (TIMEOUT_SECONDS * 1000);
+
+ synchronized (sLock) {
+ for (;;) {
+ Log.i(TAG, "waitUntilSyncCount: current count=" + sSyncCount);
+ if (sSyncCount >= expectCount) {
+ return;
+ }
+ final long sleep = timeout - SystemClock.elapsedRealtime();
+ if (sleep <= 0) {
+ break;
+ }
+ sLock.wait(sleep);
+ }
+ }
+ Assert.fail("Sync didn't happen.");
+ }
+
+ /**
+ * Request a sync on the given account, and wait for it.
+ */
+ public static void requestSync(Account account) throws Exception {
+ final int startCount = BatteryStatsSyncAdapter.getSyncCount();
+
+ final Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+
+ ContentResolver.requestSync(account, BatteryStatsProvider.AUTHORITY, extras);
+
+ waitUntilSyncCount(startCount + 1);
+ }
+
+ /**
+ * Cancel all pending sync requests on the given account.
+ */
+ public static void cancelPendingSyncs(Account account) throws Exception {
+ final long timeout = SystemClock.elapsedRealtime() + (TIMEOUT_SECONDS * 1000);
+
+ ContentResolver.cancelSync(account, BatteryStatsProvider.AUTHORITY);
+
+ for (;;) {
+ if (!ContentResolver.isSyncPending(account, BatteryStatsProvider.AUTHORITY)
+ && !ContentResolver.isSyncActive(account, BatteryStatsProvider.AUTHORITY)) {
+ return;
+ }
+ final long sleep = timeout - SystemClock.elapsedRealtime();
+ if (sleep <= 0) {
+ break;
+ }
+ Thread.sleep(sleep);
+ }
+ Assert.fail("Couldn't cancel pending sync.");
+ }
+}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsSyncService.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsSyncService.java
new file mode 100644
index 0000000..d657c1a
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsSyncService.java
@@ -0,0 +1,36 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Service for the sync test.
+ */
+public class BatteryStatsSyncService extends Service {
+
+ private static BatteryStatsSyncAdapter sAdapter;
+
+ @Override
+ public synchronized IBinder onBind(Intent intent) {
+ if (sAdapter == null) {
+ sAdapter = new BatteryStatsSyncAdapter(getApplicationContext());
+ }
+ return sAdapter.getSyncAdapterBinder();
+ }
+}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsSyncTest.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsSyncTest.java
new file mode 100644
index 0000000..daa6f5e
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsSyncTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.server.cts.device.batterystats;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsSyncTest {
+ private static final String TAG = "BatteryStatsSyncTest";
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() {
+ BatteryStatsAuthenticator.removeAllAccounts(getContext());
+ }
+
+ @After
+ public void tearDown() {
+ BatteryStatsAuthenticator.removeAllAccounts(getContext());
+ }
+
+ /**
+ * Run a sync N times and make sure it shows up in the battery stats.
+ */
+ @Test
+ public void testRunSyncs() throws Exception {
+ final Account account = BatteryStatsAuthenticator.getTestAccount();
+
+ // Create the test account.
+ BatteryStatsAuthenticator.ensureTestAccount(getContext());
+
+ // Just force set is syncable.
+ ContentResolver.setMasterSyncAutomatically(true);
+ ContentResolver.setIsSyncable(account, BatteryStatsProvider.AUTHORITY, 1);
+
+ // Cancel the initial sync.
+ BatteryStatsSyncAdapter.cancelPendingSyncs(account);
+
+ final int NUM_SYNC = 10;
+
+ // Run syncs.
+ for (int i = 0; i < NUM_SYNC; i++) {
+ BatteryStatsSyncAdapter.requestSync(account);
+ Thread.sleep(1000);
+ }
+ }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
index 287dd53..34d7215 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
@@ -31,6 +31,17 @@
= "com.android.server.cts.device.batterystats";
private static final String DEVICE_SIDE_JOB_COMPONENT
= "com.android.server.cts.device.batterystats/.SimpleJobService";
+ private static final String DEVICE_SIDE_SYNC_COMPONENT
+ = "com.android.server.cts.device.batterystats.provider/"
+ + "com.android.server.cts.device.batterystats";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Uninstall to clear the history in case it's still on the device.
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ }
@Override
protected void tearDown() throws Exception {
@@ -50,6 +61,19 @@
getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
}
+ public void testAlarms() throws Exception {
+ batteryOnScreenOff();
+
+ installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsAlarmTest", "testAlarms");
+
+ assertValueRange("wua", "*walarm*:com.android.server.cts.device.batterystats.ALARM",
+ 5, 3, 3);
+
+ batteryOffScreenOn();
+ }
+
public void testWakeLockDuration() throws Exception {
batteryOnScreenOff();
@@ -138,6 +162,26 @@
}
/**
+ * Tests the total duration and # of syncs reported for sync activities.
+ */
+ public void testSyncs() throws Exception {
+ batteryOnScreenOff();
+
+ installPackage(DEVICE_SIDE_TEST_APK, true);
+
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsSyncTest", "testRunSyncs");
+
+ // First, check the count, which should be 10.
+ // (It could be 11, if the initial sync actually happened before getting cancelled.)
+ assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 10L, 11L);
+
+ // Should be approximately, but at least 10 seconds. Use 2x as the upper
+ // bounds to account for possible errors due to thread scheduling and cpu load.
+ assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 5, 10000, 10000 * 2);
+ batteryOffScreenOn();
+ }
+
+ /**
* Verifies that the recorded time for the specified tag and name in the test package
* is within the specified range.
*/
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
index defcf4e..ff547a0 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
@@ -42,6 +42,8 @@
// Intent action that this activity dynamically registers to enter picture-in-picture
private static final String ACTION_ENTER_PIP = "android.server.cts.PipActivity.enter_pip";
+ // Intent action that this activity dynamically registers to move itself to the back
+ private static final String ACTION_MOVE_TO_BACK = "android.server.cts.PipActivity.move_to_back";
// Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
@@ -75,8 +77,15 @@
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent != null && intent.getAction().equals(ACTION_ENTER_PIP)) {
- enterPictureInPictureMode();
+ if (intent != null) {
+ switch (intent.getAction()) {
+ case ACTION_ENTER_PIP:
+ enterPictureInPictureMode();
+ break;
+ case ACTION_MOVE_TO_BACK:
+ moveTaskToBack(false /* nonRoot */);
+ break;
+ }
}
}
};
@@ -141,9 +150,18 @@
}
@Override
+ protected void onStart() {
+ super.onStart();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_ENTER_PIP);
+ filter.addAction(ACTION_MOVE_TO_BACK);
+ registerReceiver(mReceiver, filter);
+ }
+
+ @Override
protected void onResume() {
super.onResume();
- registerReceiver(mReceiver, new IntentFilter(ACTION_ENTER_PIP));
// Finish self if requested
if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
@@ -154,7 +172,6 @@
@Override
protected void onPause() {
super.onPause();
- unregisterReceiver(mReceiver);
// Pause if requested
if (getIntent().hasExtra(EXTRA_ON_PAUSE_DELAY)) {
@@ -170,6 +187,8 @@
@Override
protected void onStop() {
super.onStop();
+ unregisterReceiver(mReceiver);
+
if (getIntent().hasExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP) && !mEnteredPictureInPicture) {
Log.w("PipActivity", "Unexpected onStop() called before entering picture-in-picture");
finish();
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
index 07d4a06..697143f 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
@@ -225,11 +225,12 @@
// Press back
pressBackButton();
- mAmWmState.waitForValidState(mDevice, LAUNCHING_ACTIVITY);
+
+ mAmWmState.waitForValidState(mDevice, ALT_LAUNCHING_ACTIVITY);
// Ensure the alternate launching activity is in focus
mAmWmState.assertFocusedActivity("Alt Launching Activity must be focused",
- LAUNCHING_ACTIVITY);
+ ALT_LAUNCHING_ACTIVITY);
}
/**
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
index cc28e6a..9ebcfe1 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
@@ -453,8 +453,8 @@
final String targetActivity = " --es target_activity " + THIRD_ACTIVITY_NAME
+ " --es package_name " + THIRD_PACKAGE_NAME;
final String includeStoppedPackagesFlag = " -f 0x00000020";
- executeShellCommand("am broadcast -a " + broadcastAction + targetActivity
- + includeStoppedPackagesFlag);
+ executeShellCommand("am broadcast -a " + broadcastAction + " -p " + SECOND_PACKAGE_NAME
+ + targetActivity + includeStoppedPackagesFlag);
mAmWmState.waitForValidState(mDevice, new String[] {THIRD_ACTIVITY_NAME},
null /* stackIds */, false /* compareTaskAndStackBounds */, THIRD_PACKAGE_NAME);
@@ -490,8 +490,8 @@
// Launch other activity with same uid as owner and check if is launched on focused stack on
// secondary display.
final String broadcastAction = componentName + ".LAUNCH_BROADCAST_ACTION";
- executeShellCommand("am broadcast -a " + broadcastAction
- + " --ez new_task true --ez multiple_task true");
+ executeShellCommand("am broadcast -a " + broadcastAction + " -p " + componentName
+ + " --ez launch_activity true --ez new_task true --ez multiple_task true");
mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY_NAME);
mAmWmState.assertFocusedActivity("Focus must be on newly launched app", TEST_ACTIVITY_NAME);
@@ -523,7 +523,8 @@
// Launch other activity with different uid and check it is launched on primary display.
final String broadcastAction = SECOND_PACKAGE_NAME + ".LAUNCH_BROADCAST_ACTION";
final String includeStoppedPackagesFlag = " -f 0x00000020";
- executeShellCommand("am broadcast -a " + broadcastAction + includeStoppedPackagesFlag);
+ executeShellCommand("am broadcast -a " + broadcastAction + " -p " + SECOND_PACKAGE_NAME
+ + includeStoppedPackagesFlag);
mAmWmState.waitForValidState(mDevice, new String[] {SECOND_ACTIVITY_NAME},
null /* stackIds */, false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME);
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 1ad1251..be07e26 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
@@ -59,6 +59,8 @@
private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
"android.server.cts.PipActivity.enter_pip";
+ private static final String PIP_ACTIVITY_ACTION_MOVE_TO_BACK =
+ "android.server.cts.PipActivity.move_to_back";
private static final int APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
private static final int APP_OPS_MODE_ALLOWED = 0;
@@ -483,13 +485,8 @@
// Remove the stack and ensure that the task is now in the fullscreen stack (when no
// fullscreen stack existed before)
removeStacks(PINNED_STACK_ID);
- mAmWmState.waitForFocusedStack(mDevice, HOME_STACK_ID);
- mAmWmState.assertFocusedStack("Expect home stack focused", HOME_STACK_ID);
- mAmWmState.waitForActivityState(mDevice, PIP_ACTIVITY, STATE_STOPPED);
- assertTrue(mAmWmState.getAmState().hasActivityState(PIP_ACTIVITY, STATE_STOPPED));
- assertPinnedStackDoesNotExist();
- assertTrue(mAmWmState.getAmState().getTaskByActivityName(PIP_ACTIVITY,
- FULLSCREEN_WORKSPACE_STACK_ID) != null);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
+ true /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
}
public void testRemovePipWithVisibleFullscreenStack() throws Exception {
@@ -503,17 +500,8 @@
// Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
// top fullscreen activity
removeStacks(PINNED_STACK_ID);
- mAmWmState.waitForFocusedStack(mDevice, FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertFocusedStack("Expect fullscreen stack focused",
- FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.waitForActivityState(mDevice, PIP_ACTIVITY, STATE_STOPPED);
- assertTrue(mAmWmState.getAmState().hasActivityState(PIP_ACTIVITY, STATE_STOPPED));
- assertPinnedStackDoesNotExist();
- ActivityTask bottomTask = mAmWmState.getAmState().getStackById(
- FULLSCREEN_WORKSPACE_STACK_ID).getBottomTask();
- Activity bottomActivity = bottomTask.mActivities.get(0);
- assertTrue(bottomActivity.name.equals(ActivityManagerTestBase.getActivityComponentName(
- PIP_ACTIVITY)));
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
+ false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
}
public void testRemovePipWithHiddenFullscreenStack() throws Exception {
@@ -529,16 +517,57 @@
// Remove the stack and ensure that the task is placed on top of the hidden fullscreen
// stack, but that the home stack is still focused
removeStacks(PINNED_STACK_ID);
- mAmWmState.waitForFocusedStack(mDevice, HOME_STACK_ID);
- mAmWmState.assertFocusedStack("Expect home stack focused", HOME_STACK_ID);
- mAmWmState.waitForActivityState(mDevice, PIP_ACTIVITY, STATE_STOPPED);
- assertTrue(mAmWmState.getAmState().hasActivityState(PIP_ACTIVITY, STATE_STOPPED));
- assertPinnedStackDoesNotExist();
- ActivityTask topTask = mAmWmState.getAmState().getStackById(
- FULLSCREEN_WORKSPACE_STACK_ID).getTopTask();
- Activity topActivity = topTask.mActivities.get(0);
- assertTrue(topActivity.name.equals(ActivityManagerTestBase.getActivityComponentName(
- PIP_ACTIVITY)));
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
+ true /* expectTopTaskHasActivity */, false /* expectBottomTaskHasActivity */);
+ }
+
+ public void testMovePipToBackWithNoFullscreenStack() throws Exception {
+ if (!supportsPip()) return;
+
+ // Start with a clean slate, remove all the stacks but home
+ removeStacks(ALL_STACK_IDS_BUT_HOME);
+
+ // Launch a pip activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Remove the stack and ensure that the task is now in the fullscreen stack (when no
+ // fullscreen stack existed before)
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
+ true /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
+ }
+
+ public void testMovePipToBackWithVisibleFullscreenStack() throws Exception {
+ if (!supportsPip()) return;
+
+ // Launch a fullscreen activity, and a pip activity over that
+ launchActivity(TEST_ACTIVITY);
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
+ // top fullscreen activity
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
+ false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
+ }
+
+ public void testMovePipToBackWithHiddenFullscreenStack() throws Exception {
+ if (!supportsPip()) return;
+
+ // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
+ // launch a pip activity over home
+ launchActivity(TEST_ACTIVITY);
+ launchHomeActivity();
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
+ // stack, but that the home stack is still focused
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
+ true /* expectTopTaskHasActivity */, false /* expectBottomTaskHasActivity */);
}
public void testPinnedStackAlwaysOnTop() throws Exception {
@@ -658,6 +687,37 @@
}
/**
+ * Called after the given {@param activityName} has been moved to the fullscreen stack. Ensures
+ * that the {@param focusedStackId} is focused, and checks the top and/or bottom tasks in the
+ * fullscreen stack if {@param expectTopTaskHasActivity} or {@param expectBottomTaskHasActivity}
+ * are set respectively.
+ */
+ private void assertPinnedStackStateOnMoveToFullscreen(String activityName, int focusedStackId,
+ boolean expectTopTaskHasActivity, boolean expectBottomTaskHasActivity)
+ throws Exception {
+ mAmWmState.waitForFocusedStack(mDevice, focusedStackId);
+ mAmWmState.assertFocusedStack("Wrong focused stack", focusedStackId);
+ mAmWmState.waitForActivityState(mDevice, activityName, STATE_STOPPED);
+ assertTrue(mAmWmState.getAmState().hasActivityState(activityName, STATE_STOPPED));
+ assertPinnedStackDoesNotExist();
+
+ if (expectTopTaskHasActivity) {
+ ActivityTask topTask = mAmWmState.getAmState().getStackById(
+ FULLSCREEN_WORKSPACE_STACK_ID).getTopTask();
+ Activity topActivity = topTask.mActivities.get(0);
+ assertTrue(topActivity.name.equals(ActivityManagerTestBase.getActivityComponentName(
+ activityName)));
+ }
+ if (expectBottomTaskHasActivity) {
+ ActivityTask bottomTask = mAmWmState.getAmState().getStackById(
+ FULLSCREEN_WORKSPACE_STACK_ID).getBottomTask();
+ Activity bottomActivity = bottomTask.mActivities.get(0);
+ assertTrue(bottomActivity.name.equals(ActivityManagerTestBase.getActivityComponentName(
+ activityName)));
+ }
+ }
+
+ /**
* Asserts that the pinned stack bounds does not intersect with the IME bounds.
*/
private void assertPinnedStackDoesNotIntersectIME() throws Exception {
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java
index 85e2504..5b2eb14 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java
@@ -626,6 +626,11 @@
while (!doneExtracting(dump, exitPatterns)) {
final String line = dump.pop().trim();
+ // Break the activity extraction once we hit an empty line
+ if (line.isEmpty()) {
+ break;
+ }
+
Matcher matcher = VISIBILITY_PATTERN.matcher(line);
if (matcher.matches()) {
log(line);
diff --git a/tests/accessibility/res/values/strings.xml b/tests/accessibility/res/values/strings.xml
index 47ba12c..867f466 100644
--- a/tests/accessibility/res/values/strings.xml
+++ b/tests/accessibility/res/values/strings.xml
@@ -26,4 +26,7 @@
<!-- Description of the speaking accessibility service -->
<string name="some_description">Some description</string>
+ <!-- Summary of the speaking accessibility service -->
+ <string name="some_summary">Some summary</string>
+
</resources>
diff --git a/tests/accessibility/res/xml/speaking_accessibilityservice.xml b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
index c60fb54..5bc5a33 100644
--- a/tests/accessibility/res/xml/speaking_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
@@ -22,4 +22,5 @@
android:canRequestFilterKeyEvents="true"
android:canRequestEnhancedWebAccessibility="true"
android:settingsActivity="foo.bar.Activity"
- android:description="@string/some_description" />
+ android:description="@string/some_description"
+ android:summary="@string/some_summary" />
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
index bd14ee6..b90178a 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
@@ -79,6 +79,8 @@
assertEquals("foo.bar.Activity", speakingService.getSettingsActivityName());
assertEquals("Some description", speakingService.loadDescription(
getInstrumentation().getContext().getPackageManager()));
+ assertEquals("Some summary", speakingService.loadSummary(
+ getInstrumentation().getContext().getPackageManager()));
assertNotNull(speakingService.getResolveInfo());
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
index a933099..1761117 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
@@ -98,7 +98,6 @@
AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS));
assertEquals("FLAG_REQUEST_TOUCH_EXPLORATION_MODE", AccessibilityServiceInfo.flagToString(
AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE));
-
}
/**
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 7102c32..a5ea701 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -312,17 +312,6 @@
</intent-filter>
</activity>
- <activity android:name="android.app.stubs.PipNotResizeableActivity"
- android:label="PipNotResizeableActivity"
- android:resizeableActivity="false"
- android:supportsPictureInPicture="true"
- android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
<activity android:name="android.app.stubs.PipNotSupportedActivity"
android:label="PipNotSupportedActivity"
android:resizeableActivity="true"
diff --git a/tests/app/app/src/android/app/stubs/PipNotResizeableActivity.java b/tests/app/app/src/android/app/stubs/PipNotResizeableActivity.java
deleted file mode 100644
index 7ed1acc..0000000
--- a/tests/app/app/src/android/app/stubs/PipNotResizeableActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.app.stubs;
-
-import android.app.Activity;
-
-public class PipNotResizeableActivity extends Activity {
-
-}
diff --git a/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java b/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java
deleted file mode 100644
index e2a4aff..0000000
--- a/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.app.cts;
-
-import android.app.Instrumentation;
-import android.app.stubs.PipNotResizeableActivity;
-import android.test.ActivityInstrumentationTestCase2;
-
-public class PipNotResizeableActivityTest
- extends ActivityInstrumentationTestCase2<PipNotResizeableActivity> {
-
- private Instrumentation mInstrumentation;
- private PipNotResizeableActivity mActivity;
-
- public PipNotResizeableActivityTest() {
- super("android.app.stubs", PipNotResizeableActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mInstrumentation = getInstrumentation();
- mActivity = getActivity();
- }
-
- public void testLaunchPipNotResizeableActivity() throws Throwable {
- runTestOnUiThread(new Runnable() {
- public void run() {
- boolean pipSupportDisabled = false;
- try {
- mActivity.enterPictureInPictureMode();
- } catch (IllegalStateException e) {
- // Pip not supported
- pipSupportDisabled = true;
- } catch (IllegalArgumentException e) {
- // Pip not supported
- pipSupportDisabled = true;
- }
- assertTrue(pipSupportDisabled);
- assertFalse(mActivity.isInPictureInPictureMode());
- }
- });
- mInstrumentation.waitForIdleSync();
- }
- }
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 869bff6..2662445 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -36,6 +36,7 @@
<activity android:name=".DatePickerSpinnerActivity" />
<activity android:name=".TimePickerClockActivity" />
<activity android:name=".TimePickerSpinnerActivity" />
+ <activity android:name=".FatActivity" />
<service
android:name=".InstrumentedAutoFillService"
diff --git a/tests/autofillservice/res/drawable/android.png b/tests/autofillservice/res/drawable/android.png
new file mode 100644
index 0000000..8a9e698
--- /dev/null
+++ b/tests/autofillservice/res/drawable/android.png
Binary files differ
diff --git a/tests/autofillservice/res/layout/fat_activity.xml b/tests/autofillservice/res/layout/fat_activity.xml
new file mode 100644
index 0000000..c7853c1
--- /dev/null
+++ b/tests/autofillservice/res/layout/fat_activity.xml
@@ -0,0 +1,69 @@
+<?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"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Label with no ID" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/input_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <EditText
+ android:id="@+id/input"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <EditText
+ android:id="@+id/captcha"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAutofill="no"
+ android:text="Y U NO CAPTCHA ME?" />
+
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/android" />
+
+ <ImageView
+ android:id="@+id/important_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAutofill="yes"
+ android:src="@drawable/android" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/login_activity.xml b/tests/autofillservice/res/layout/login_activity.xml
index fc3e6f4..ba60a79 100644
--- a/tests/autofillservice/res/layout/login_activity.xml
+++ b/tests/autofillservice/res/layout/login_activity.xml
@@ -24,6 +24,7 @@
android:orientation="vertical" >
<LinearLayout
+ android:id="@+id/username_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
index 652fae3..4873c16 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
@@ -12,9 +12,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */package android.autofillservice.cts;
+ */
+
+package android.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
import android.app.Activity;
+import android.view.autofill.AutoFillManager;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -24,6 +29,8 @@
*/
abstract class AbstractAutoFillActivity extends Activity {
+ private MyAutofillCallback mCallback;
+
/**
* Run an action in the UI thread, and blocks caller until the action is finished.
*/
@@ -43,11 +50,32 @@
});
try {
if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
- throw new AssertionError("action on UI thread timed out after " + timeoutMs + " ms");
+ throw new AssertionError(
+ "action on UI thread timed out after " + timeoutMs + " ms");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted", e);
}
}
+
+ /**
+ * Registers and returns a custom callback for autofill events.
+ */
+ protected MyAutofillCallback registerCallback() {
+ assertWithMessage("already registered").that(mCallback).isNull();
+ final AutoFillManager afm = getSystemService(AutoFillManager.class);
+ mCallback = new MyAutofillCallback();
+ afm.registerCallback(mCallback);
+ return mCallback;
+ }
+
+ /**
+ * Unregister the callback from the {@link AutoFillManager}.
+ */
+ protected void unregisterCallback() {
+ assertWithMessage("not registered").that(mCallback).isNotNull();
+ getSystemService(AutoFillManager.class).unregisterCallback(mCallback);
+ mCallback = null;
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index a3c4026..cb0a911 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -49,7 +49,7 @@
@BeforeClass
public static void removeLockScreen() {
runShellCommand("input keyevent KEYCODE_WAKEUP");
- runShellCommand("input keyevent 82");
+ runShellCommand("wm dismiss-keyguard");
}
@BeforeClass
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index f822568..2fa207b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -17,13 +17,10 @@
import static com.google.common.truth.Truth.assertWithMessage;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
import static android.autofillservice.cts.Helper.dumpStructure;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset.Builder;
import android.content.IntentSender;
import android.os.Bundle;
import android.service.autofill.Dataset;
@@ -91,19 +88,21 @@
builder.addDataset(dataset);
}
}
- if (savableIds != null) {
+ if (saveType >= 0 ) {
final SaveInfo.Builder saveInfo = new SaveInfo.Builder(saveType);
if (saveDescription != null) {
saveInfo.setDescription(saveDescription);
}
- for (String resourceId : savableIds) {
- final ViewNode node = findNodeByResourceId(structure, resourceId);
- if (node == null) {
- dumpStructure("onFillRequest()", structure);
- throw new AssertionError("No node with savable resourceId " + resourceId);
+ if (savableIds != null) {
+ for (String resourceId : savableIds) {
+ final ViewNode node = findNodeByResourceId(structure, resourceId);
+ if (node == null) {
+ dumpStructure("onFillRequest()", structure);
+ throw new AssertionError("No node with savable resourceId " + resourceId);
+ }
+ final AutoFillId id = node.getAutoFillId();
+ saveInfo.addSavableIds(id);
}
- final AutoFillId id = node.getAutoFillId();
- saveInfo.addSavableIds(id);
}
if (negativeActionLabel != null) {
saveInfo.setNegativeAction(negativeActionLabel, negativeActionListener);
@@ -130,7 +129,7 @@
private final List<CannedDataset> mDatasets = new ArrayList<>();
private String[] mSavableIds;
private String mSaveDescription;
- public int mSaveType;
+ public int mSaveType = -1;
private Bundle mExtras;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
@@ -145,7 +144,8 @@
/**
* Sets the savable ids based on they {@code resourceId}.
*/
- public Builder setSavableIds(String... ids) {
+ public Builder setSavableIds(int type, String... ids) {
+ mSaveType = type;
mSavableIds = ids;
return this;
}
@@ -165,6 +165,7 @@
mSaveType = type;
return this;
}
+
/**
* Sets the extra passed to {@link
* android.service.autofill.FillResponse.Builder#setExtras(Bundle)}.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
index 91d1d43..4eb4873 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
@@ -33,6 +33,7 @@
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -128,7 +129,8 @@
// Set expectations.
replier.addResponse(new CannedFillResponse.Builder()
- .setSavableIds(ID_CC_NUMBER, ID_CC_EXPIRATION, ID_ADDRESS, ID_SAVE_CC)
+ .setSavableIds(SAVE_DATA_TYPE_CREDIT_CARD,
+ ID_CC_NUMBER, ID_CC_EXPIRATION, ID_ADDRESS, ID_SAVE_CC)
.build());
// Dynamically change view contents
@@ -162,7 +164,7 @@
mCheckoutActivity.onSaveCc((v) -> { v.setChecked(true); });
mCheckoutActivity.tapBuy();
InstrumentedAutoFillService.setReplier(replier); // Replier was reset onFill()
- sUiBot.saveForAutofill(true);
+ sUiBot.saveForAutofill(SAVE_DATA_TYPE_CREDIT_CARD, true);
final SaveRequest saveRequest = replier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
index b9aecb5..c90b348 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
@@ -24,6 +24,8 @@
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
import static com.google.common.truth.Truth.assertWithMessage;
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
@@ -64,10 +66,13 @@
cal.set(Calendar.MONTH, 11);
cal.set(Calendar.DAY_OF_MONTH, 20);
- replier.addResponse(new CannedDataset.Builder()
- .setPresentation(createPresentation("The end of the world"))
- .setField(ID_OUTPUT, AutoFillValue.forText("Y U NO CHANGE ME?"))
- .setField(ID_DATE_PICKER, AutoFillValue.forDate(cal.getTimeInMillis()))
+ replier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setPresentation(createPresentation("The end of the world"))
+ .setField(ID_OUTPUT, AutoFillValue.forText("Y U NO CHANGE ME?"))
+ .setField(ID_DATE_PICKER, AutoFillValue.forDate(cal.getTimeInMillis()))
+ .build())
+ .setSaveType(SAVE_DATA_TYPE_GENERIC)
.build());
activity.expectAutoFill("2012/11/20", 2012, 11, 20);
@@ -92,7 +97,7 @@
activity.tapOk();
InstrumentedAutoFillService.setReplier(replier); // Replier was reset onFill()
- sUiBot.saveForAutofill(true);
+ sUiBot.saveForAutofill(SAVE_DATA_TYPE_GENERIC, true);
final SaveRequest saveRequest = replier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
new file mode 100644
index 0000000..167eb2d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.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 android.autofillservice.cts;
+
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+/**
+ * An activity containing mostly widgets that should be removed from an auto-fill structure to
+ * optimize it.
+ */
+public class FatActivity extends AbstractAutoFillActivity {
+
+ static final String ID_CAPTCHA = "captcha";
+ static final String ID_INPUT = "input";
+ static final String ID_INPUT_CONTAINER = "input_container";
+ static final String ID_IMAGE = "image";
+ static final String ID_IMPORTANT_IMAGE = "important_image";
+
+ private EditText mCaptcha;
+ private EditText mInput;
+ private ImageView mImage;
+ private ImageView mImportantImage;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.fat_activity);
+
+ mCaptcha = (EditText) findViewById(R.id.captcha);
+ mInput = (EditText) findViewById(R.id.input);
+ mImage = (ImageView) findViewById(R.id.image);
+ mImportantImage = (ImageView) findViewById(R.id.important_image);
+
+ // Sanity check for importantForAutofill modes
+ assertThat(mInput.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+ assertThat(mCaptcha.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+ assertThat(mImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+ assertThat(mImportantImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+
+ }
+
+ /**
+ * Visits the {@code input} in the UiThread.
+ */
+ void onInput(ViewVisitor<EditText> v) {
+ syncRunOnUiThread(() -> {
+ v.visit(mInput);
+ });
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
new file mode 100644
index 0000000..c2ec922
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.FatActivity.ID_CAPTCHA;
+import static android.autofillservice.cts.FatActivity.ID_IMAGE;
+import static android.autofillservice.cts.FatActivity.ID_IMPORTANT_IMAGE;
+import static android.autofillservice.cts.FatActivity.ID_INPUT;
+import static android.autofillservice.cts.FatActivity.ID_INPUT_CONTAINER;
+import static android.autofillservice.cts.Helper.assertNumberOfChildren;
+import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.Helper.findNodeByText;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.InstrumentedAutoFillService.Replier;
+import android.support.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test case for an activity containing useless auto-fill data that should be optimized out.
+ */
+public class FatActivityTest extends AutoFillServiceTestCase {
+
+ @Rule
+ public final ActivityTestRule<FatActivity> mActivityRule =
+ new ActivityTestRule<FatActivity>(FatActivity.class);
+
+ private FatActivity mFatActivity;
+
+ @Before
+ public void setActivity() {
+ mFatActivity = mActivityRule.getActivity();
+ }
+
+ @Test
+ public void testNoContainers() throws Exception {
+ // Set service.
+ enableService();
+ final Replier replier = new Replier();
+ InstrumentedAutoFillService.setReplier(replier);
+
+ // Set expectations.
+ replier.addResponse((CannedFillResponse) null);
+
+ // Trigger auto-fill.
+ mFatActivity.onInput((v) -> { v.requestFocus(); });
+ waitUntilConnected();
+ sUiBot.assertNoDatasets();
+
+ final FillRequest fillRequest = replier.getNextFillRequest();
+
+ // TODO(b/33197203, b/33802548): should only have 5 children, but there is an extra
+ // TextView that's probably coming from the title. For now we're just ignoring it, but
+ // ideally we should change the .xml to exclude it.
+ assertNumberOfChildren(fillRequest.structure, 6);
+
+ // Should not have ImageView...
+ assertThat(findNodeByResourceId(fillRequest.structure, ID_IMAGE)).isNull();
+
+ // ...unless app developer asked to:
+ assertThat(findNodeByResourceId(fillRequest.structure, ID_IMPORTANT_IMAGE)).isNotNull();
+
+ // Should have TextView, even if it does not have id.
+ assertThat(findNodeByText(fillRequest.structure, "Label with no ID")).isNotNull();
+
+ // Should not have EditText that was explicitly removed.
+ assertThat(findNodeByResourceId(fillRequest.structure, ID_CAPTCHA)).isNull();
+
+ // Make sure container with a resource id was included.
+ final ViewNode inputContainer =
+ findNodeByResourceId(fillRequest.structure, ID_INPUT_CONTAINER);
+ assertThat(inputContainer).isNotNull();
+ assertThat(inputContainer.getChildCount()).isEqualTo(1);
+ final ViewNode input = inputContainer.getChildAt(0);
+ assertThat(input.getIdEntry()).isEqualTo(ID_INPUT);
+
+ // Sanity checks.
+ replier.assertNumberUnhandledFillRequests(0);
+ replier.assertNumberUnhandledSaveRequests(0);
+
+ // Other sanity checks.
+ waitUntilDisconnected();
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index dd99b6d..c756f7f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -106,6 +106,7 @@
buffer.append("\n").append(prefix)
.append('#').append(childId).append(':')
.append("resId=").append(node.getIdEntry())
+ .append(" class=").append(node.getClassName())
.append(" text=").append(node.getText())
.append(" #children=").append(childrenSize);
@@ -124,7 +125,7 @@
}
/**
- * Gets a node give its Android resource id, or {@code null} if not found.
+ * Gets a node given its Android resource id, or {@code null} if not found.
*/
static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
@@ -141,7 +142,7 @@
}
/**
- * Gets a node give its Android resource id, or {@code null} if not found.
+ * Gets a node given its Android resource id, or {@code null} if not found.
*/
static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
if (resourceId.equals(node.getIdEntry())) {
@@ -160,6 +161,42 @@
}
/**
+ * Gets a node given its expected text, or {@code null} if not found.
+ */
+ static ViewNode findNodeByText(AssistStructure structure, String text) {
+ Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
+ final int nodes = structure.getWindowNodeCount();
+ for (int i = 0; i < nodes; i++) {
+ final WindowNode windowNode = structure.getWindowNodeAt(i);
+ final ViewNode rootNode = windowNode.getRootViewNode();
+ final ViewNode node = findNodeByText(rootNode, text);
+ if (node != null) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets a node given its expected text, or {@code null} if not found.
+ */
+ static ViewNode findNodeByText(ViewNode node, String text) {
+ if (text.equals(node.getText())) {
+ return node;
+ }
+ final int childrenSize = node.getChildCount();
+ if (childrenSize > 0) {
+ for (int i = 0; i < childrenSize; i++) {
+ final ViewNode found = findNodeByText(node.getChildAt(i), text);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Asserts a text-base node is sanitized.
*/
static void assertTextIsSanitized(ViewNode node) {
@@ -303,6 +340,48 @@
}
}
+ /**
+ * Asserts the number of children in the Assist structure.
+ */
+ static void assertNumberOfChildren(AssistStructure structure, int expected) {
+ assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount())
+ .isEqualTo(1);
+ final int actual = getNumberNodes(structure);
+ if (actual != expected) {
+ dumpStructure("assertNumberOfChildren()", structure);
+ throw new AssertionError("assertNumberOfChildren() for structure failed: expected "
+ + expected + ", got " + actual);
+ }
+ }
+
+ /**
+ * Gets the total number of nodes in an structure, including all descendants.
+ */
+ static int getNumberNodes(AssistStructure structure) {
+ int count = 0;
+ final int nodes = structure.getWindowNodeCount();
+ for (int i = 0; i < nodes; i++) {
+ final WindowNode windowNode = structure.getWindowNodeAt(i);
+ final ViewNode rootNode = windowNode.getRootViewNode();
+ count += getNumberNodes(rootNode);
+ }
+ return count;
+ }
+
+ /**
+ * Gets the total number of nodes in an node, including all descendants and the node itself.
+ */
+ private static int getNumberNodes(ViewNode node) {
+ int count = 1;
+ final int childrenSize = node.getChildCount();
+ if (childrenSize > 0) {
+ for (int i = 0; i < childrenSize; i++) {
+ count += getNumberNodes(node.getChildAt(i));
+ }
+ }
+ return count;
+ }
+
private Helper() {
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index 3f12e27..7f88942 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -46,12 +46,12 @@
private static final long LOGIN_TIMEOUT_MS = 1000;
static final String AUTHENTICATION_MESSAGE = "Authentication failed. D'OH!";
+ static final String ID_USERNAME_CONTAINER = "username_container";
static final String ID_USERNAME_LABEL = "username_label";
static final String ID_USERNAME = "username";
static final String ID_PASSWORD_LABEL = "password_label";
static final String ID_PASSWORD = "password";
static final String ID_LOGIN = "login";
- static final String ID_OUTPUT = "output";
private TextView mUsernameLabel;
private EditText mUsernameEditText;
@@ -180,6 +180,13 @@
}
/**
+ * Gets the {@code username} view.
+ */
+ EditText getUsername() {
+ return mUsernameEditText;
+ }
+
+ /**
* Visits the {@code password_label} in the UiThread.
*/
void onPasswordLabel(ViewVisitor<TextView> v) {
@@ -198,6 +205,13 @@
}
/**
+ * Gets the {@code password} view.
+ */
+ EditText getPassword() {
+ return mPasswordEditText;
+ }
+
+ /**
* Taps the login button in the UI thread.
*/
String tapLogin() throws Exception {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index e812a4f..abf337b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -15,6 +15,7 @@
*/
package android.autofillservice.cts;
+import static android.autofillservice.cts.Helper.assertNumberOfChildren;
import static android.autofillservice.cts.Helper.assertTextAndValue;
import static android.autofillservice.cts.Helper.assertTextIsSanitized;
import static android.autofillservice.cts.Helper.assertTextOnly;
@@ -26,11 +27,13 @@
import static android.autofillservice.cts.LoginActivity.ID_PASSWORD;
import static android.autofillservice.cts.LoginActivity.ID_PASSWORD_LABEL;
import static android.autofillservice.cts.LoginActivity.ID_USERNAME;
+import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
import static android.autofillservice.cts.LoginActivity.ID_USERNAME_LABEL;
import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
import static android.provider.Settings.Secure.AUTO_FILL_SERVICE;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
import static android.text.InputType.TYPE_NULL;
import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
@@ -54,6 +57,8 @@
import android.os.SystemClock;
import android.support.test.rule.ActivityTestRule;
import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillValue;
import org.junit.After;
@@ -75,7 +80,7 @@
*
* Save
* - test cases where only non-savable-ids are changed
- * - test case where 'no thanks' is tapped
+ * - test case where 'no thanks' is tapped (similar to testSaveSnackBarGoesAway())
*
* Other assertions
* - illegal state thrown on callback calls
@@ -175,6 +180,9 @@
public void testAutoFillOneDatasetAndMoveFocusAround() throws Exception {
// Set service.
enableService();
+ // TODO(b/35948916): callback assertions are redundant here because they're tested by
+ // testAutofillCallbacks(), but that test would not detect the extra call.
+ final MyAutofillCallback callback = mLoginActivity.registerCallback();
final Replier replier = new Replier();
InstrumentedAutoFillService.setReplier(replier);
@@ -189,10 +197,29 @@
// Trigger auto-fill.
mLoginActivity.onUsername((v) -> { v.requestFocus(); });
waitUntilConnected();
+ final FillRequest fillRequest = replier.getNextFillRequest();
+ final View username = mLoginActivity.getUsername();
+ final View password = mLoginActivity.getPassword();
+
+ callback.assertUiShownEvent(username);
// Make sure tapping on other fields from the dataset does not trigger it again
mLoginActivity.onPassword((v) -> { v.requestFocus(); });
+ replier.assertNumberUnhandledFillRequests(0);
+
+ callback.assertUiHiddenEvent(username);
+ callback.assertUiShownEvent(password);
+
mLoginActivity.onUsername((v) -> { v.requestFocus(); });
+ replier.assertNumberUnhandledFillRequests(0);
+
+ callback.assertUiHiddenEvent(password);
+ callback.assertUiShownEvent(username);
+
+ // TODO(b/35948916): temporary hack since UI is sending 2 events instead of 1
+ callback.assertUiShownEvent(username);
+
+ callback.assertNumberUnhandledEvents(0);
// Auto-fill it.
sUiBot.selectDataset("The Dude");
@@ -205,7 +232,50 @@
mLoginActivity.onUsername((v) -> { v.requestFocus(); });
// Sanity checks.
- replier.assertNumberUnhandledFillRequests(1);
+ waitUntilDisconnected();
+ }
+
+ @Test
+ public void testAutofillCallbacks() throws Exception {
+ // Set service.
+ enableService();
+ final MyAutofillCallback callback = mLoginActivity.registerCallback();
+ final Replier replier = new Replier();
+ InstrumentedAutoFillService.setReplier(replier);
+
+ // Set expectations.
+ replier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutoFillValue.forText("dude"))
+ .setField(ID_PASSWORD, AutoFillValue.forText("sweet"))
+ .setPresentation(createPresentation("The Dude"))
+ .build());
+ mLoginActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger auto-fill.
+ mLoginActivity.onUsername((v) -> { v.requestFocus(); });
+ waitUntilConnected();
+ final FillRequest fillRequest = replier.getNextFillRequest();
+ final View username = mLoginActivity.getUsername();
+ final View password = mLoginActivity.getPassword();
+
+ callback.assertUiShownEvent(username);
+
+ mLoginActivity.onPassword((v) -> { v.requestFocus(); });
+ callback.assertUiHiddenEvent(username);
+ callback.assertUiShownEvent(password);
+
+ mLoginActivity.onUsername((v) -> { v.requestFocus(); });
+ mLoginActivity.unregisterCallback();
+ callback.assertNumberUnhandledEvents(0);
+
+ // Auto-fill it.
+ sUiBot.selectDataset("The Dude");
+
+ // Check the results.
+ mLoginActivity.assertAutoFilled();
+
+ // Sanity checks.
+ callback.assertNumberUnhandledEvents(0);
waitUntilDisconnected();
}
@@ -228,6 +298,7 @@
.setField(ID_PASSWORD, AutoFillValue.forText("sweet"))
.setPresentation(createPresentation("The Dude"))
.build())
+ .setSaveType(SAVE_DATA_TYPE_PASSWORD)
.setExtras(extras)
.build());
mLoginActivity.expectAutoFill("dude", "sweet");
@@ -257,7 +328,7 @@
// Assert the snack bar is shown and tap "Save".
InstrumentedAutoFillService.setReplier(replier); // Replier was reset onFill()
- sUiBot.saveForAutofill(true);
+ sUiBot.saveForAutofill(SAVE_DATA_TYPE_PASSWORD, true);
final SaveRequest saveRequest = replier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
@@ -369,7 +440,7 @@
// Set expectations.
replier.addResponse(new CannedFillResponse.Builder()
- .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .setSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
.build());
// Trigger auto-fill.
@@ -395,7 +466,7 @@
InstrumentedAutoFillService.setReplier(replier); // Replier was reset onFill()
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true);
+ sUiBot.saveForAutofill(SAVE_DATA_TYPE_PASSWORD, true);
final SaveRequest saveRequest = replier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
@@ -432,10 +503,9 @@
final Replier replier = new Replier();
InstrumentedAutoFillService.setReplier(replier);
-
// Set expectations.
replier.addResponse(new CannedFillResponse.Builder()
- .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .setSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
.build());
// Trigger auto-fill.
@@ -461,10 +531,9 @@
InstrumentedAutoFillService.setReplier(replier); // Replier was reset onFill()
// Assert the snack bar is shown.
- sUiBot.assertSaveShowing();
+ sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
SystemClock.sleep(timeout);
- sUiBot.assertSaveNotShowing();
-
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
// Sanity check: once timed out, session should be finsihed.
assertNoDanglingSessions();
@@ -477,6 +546,11 @@
}
@Test
+ public void testGenericSave() throws Exception {
+ customizedSaveTest(SAVE_DATA_TYPE_GENERIC);
+ }
+
+ @Test
public void testCustomizedSavePassword() throws Exception {
customizedSaveTest(SAVE_DATA_TYPE_PASSWORD);
}
@@ -501,9 +575,8 @@
// Set expectations.
final String saveDescription = "Your data will be saved with love and care...";
replier.addResponse(new CannedFillResponse.Builder()
- .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .setSavableIds(type, ID_USERNAME, ID_PASSWORD)
.setSaveDescription(saveDescription)
- .setSaveType(type)
.build());
// Trigger auto-fill.
@@ -662,7 +735,7 @@
// Set expectations.
replier.addResponse(new CannedFillResponse.Builder()
- .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .setSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
.build());
// Change view contents.
@@ -691,7 +764,9 @@
mLoginActivity.onPassword((v) -> { v.setText("malkovich"); });
mLoginActivity.tapLogin();
InstrumentedAutoFillService.setReplier(replier); // Replier was reset onFill()
- sUiBot.saveForAutofill(true);
+
+ // Assert the snack bar is shown and tap "Save".
+ sUiBot.saveForAutofill(SAVE_DATA_TYPE_PASSWORD, true);
final SaveRequest saveRequest = replier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
@@ -716,7 +791,7 @@
// Set no-op behavior.
final Replier replier = new Replier();
replier.addResponse(new CannedFillResponse.Builder()
- .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .setSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
.build());
InstrumentedAutoFillService.setReplier(replier);
@@ -742,7 +817,7 @@
// Set no-op behavior.
final Replier replier = new Replier();
replier.addResponse(new CannedFillResponse.Builder()
- .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .setSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
.build());
InstrumentedAutoFillService.setReplier(replier);
@@ -775,7 +850,7 @@
getContext(), 0, new Intent(intentAction), 0).getIntentSender();
replier.addResponse(new CannedFillResponse.Builder()
- .setSavableIds(ID_USERNAME, ID_PASSWORD)
+ .setSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
.setNegativeAction("Foo", listener)
.build());
InstrumentedAutoFillService.setReplier(replier);
@@ -804,7 +879,7 @@
}, intentFilter);
// Trigger the negative button.
- sUiBot.saveForAutofill(false);
+ sUiBot.saveForAutofill(SAVE_DATA_TYPE_PASSWORD, false);
// Wait for the custom action.
assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
@@ -822,7 +897,9 @@
replier.addResponse((CannedFillResponse) null);
// Trigger auto-fill.
- mLoginActivity.onUsername((v) -> { v.requestFocus(); });
+ mLoginActivity.onUsername((v) -> {
+ v.requestFocus();
+ });
// Assert input text on fill request:
final FillRequest fillRequest = replier.getNextFillRequest();
@@ -834,4 +911,50 @@
.that(password.getInputType() & TYPE_TEXT_VARIATION_PASSWORD)
.isEqualTo(TYPE_TEXT_VARIATION_PASSWORD);
}
-}
+
+ @Test
+ public void testNoContainers() throws Exception {
+ // Set service.
+ enableService();
+ final Replier replier = new Replier();
+ InstrumentedAutoFillService.setReplier(replier);
+
+ // Set expectations.
+ replier.addResponse((CannedFillResponse) null);
+
+ // Trigger auto-fill.
+ mLoginActivity.onUsername((v) -> { v.requestFocus(); });
+ waitUntilConnected();
+ sUiBot.assertNoDatasets();
+
+ final FillRequest fillRequest = replier.getNextFillRequest();
+
+ // Assert it only has 1 root view with 8 "leaf" nodes:
+ // 1.text view for app title
+ // 2.username text label
+ // 3.username text field
+ // 4.password text label
+ // 5.password text field
+ // 6.output text field
+ // 7.clear button
+ // 8.login button
+ //
+ // But it also has an intermediate container (for username) that should be included because
+ // it has a resource id.
+
+ assertNumberOfChildren(fillRequest.structure, 10);
+
+ // Make sure container with a resource id was included:
+ final ViewNode usernameContainer =
+ findNodeByResourceId(fillRequest.structure, ID_USERNAME_CONTAINER);
+ assertThat(usernameContainer).isNotNull();
+ assertThat(usernameContainer.getChildCount()).isEqualTo(2);
+
+ // Sanity checks.
+ replier.assertNumberUnhandledFillRequests(0);
+ replier.assertNumberUnhandledSaveRequests(0);
+
+ // Other sanity checks.
+ waitUntilDisconnected();
+ }
+}
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
new file mode 100644
index 0000000..fcebed1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.CONNECTION_TIMEOUT_MS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.view.View;
+import android.view.autofill.AutoFillManager.AutofillCallback;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link AutofillCallback} used to recover events during tests.
+ */
+final class MyAutofillCallback extends AutofillCallback {
+
+ private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onAutofillEvent(View view, int event) {
+ mEvents.offer(new MyEvent(view, event));
+ }
+
+ @Override
+ public void onAutofillEventVirtual(View view, int childId, int event) {
+ throw new UnsupportedOperationException("onAutofillEventVirtual(" + view + ", " + childId
+ + ", " + event);
+ }
+
+ /**
+ * Gets the next available event or fail if it times out.
+ */
+ MyEvent getEvent() throws InterruptedException {
+ final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ if (event == null) {
+ throw new AssertionError("no event in " + CONNECTION_TIMEOUT_MS + " ms");
+ }
+ return event;
+ }
+
+ /**
+ * Used to assert there is no event left behind.
+ */
+ void assertNumberUnhandledEvents(int expected) {
+ assertWithMessage("Invalid number of events left").that(mEvents.size()).isEqualTo(expected);
+ }
+
+ /**
+ * Convenience method to assert an UI shown event for the given view was received.
+ */
+ void assertUiShownEvent(View expectedView) throws InterruptedException {
+ final MyEvent event = getEvent();
+ assertWithMessage("Invalid type on event %s", event).that(event.event)
+ .isEqualTo(EVENT_INPUT_SHOWN);
+ assertWithMessage("Invalid view on event %s", event).that(event.view)
+ .isSameAs(expectedView);
+ }
+
+ /**
+ * Convenience method to assert an UI hidden event for the given view was received.
+ */
+ void assertUiHiddenEvent(View expectedView) throws InterruptedException {
+ final MyEvent event = getEvent();
+ assertWithMessage("Invalid type on event %s", event).that(event.event)
+ .isEqualTo(EVENT_INPUT_HIDDEN);
+ assertWithMessage("Invalid view on event %s", event).that(event.view)
+ .isSameAs(expectedView);
+ }
+
+ private static final class MyEvent {
+ public final View view;
+ public final int childId;
+ public final int event;
+
+ MyEvent(View view, int event) {
+ this(view, View.NO_ID, event);
+ }
+
+ MyEvent(View view, int childId, int event) {
+ this.view = view;
+ this.childId = childId;
+ this.event = event;
+ }
+
+ @Override
+ public String toString() {
+ return event + ": " + view + " (childId: " + childId + ")";
+ }
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
index 0ba1c4b..d298281 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
@@ -22,6 +22,7 @@
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
import static android.autofillservice.cts.AbstractTimePickerActivity.ID_OUTPUT;
import static android.autofillservice.cts.AbstractTimePickerActivity.ID_TIME_PICKER;
@@ -64,11 +65,15 @@
cal.set(Calendar.HOUR_OF_DAY, 4);
cal.set(Calendar.MINUTE, 20);
- replier.addResponse(new CannedDataset.Builder()
- .setPresentation(createPresentation("Adventure Time"))
- .setField(ID_OUTPUT, AutoFillValue.forText("Y U NO CHANGE ME?"))
- .setField(ID_TIME_PICKER, AutoFillValue.forDate(cal.getTimeInMillis()))
+ replier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setPresentation(createPresentation("Adventure Time"))
+ .setField(ID_OUTPUT, AutoFillValue.forText("Y U NO CHANGE ME?"))
+ .setField(ID_TIME_PICKER, AutoFillValue.forDate(cal.getTimeInMillis()))
+ .build())
+ .setSaveType(SAVE_DATA_TYPE_GENERIC)
.build());
+
activity.expectAutoFill("4:20", 4, 20);
// Trigger auto-fill.
@@ -91,7 +96,7 @@
activity.tapOk();
InstrumentedAutoFillService.setReplier(replier); // Replier was reset onFill()
- sUiBot.saveForAutofill(true);
+ sUiBot.saveForAutofill(SAVE_DATA_TYPE_GENERIC, true);
final SaveRequest saveRequest = replier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index a2ceafb..17942b5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -110,19 +110,19 @@
/**
* Asserts the save snackbar is showing and returns it.
*/
- UiObject2 assertSaveShowing() {
- return assertSaveShowing(SAVE_DATA_TYPE_GENERIC, null);
+ UiObject2 assertSaveShowing(int type) {
+ return assertSaveShowing(type, null);
}
/**
* Asserts the save snackbar is not showing and returns it.
*/
- void assertSaveNotShowing() {
+ void assertSaveNotShowing(int type) {
try {
- assertSaveShowing();
+ assertSaveShowing(type);
} catch (Throwable t) {
// TODO(b/33197203): use a more elegant check than catching the expection because it's
- // not showing...
+ // not showing (in which case it wouldn't need a type as parameter).
return;
}
throw new AssertionError("snack bar is showing");
@@ -178,11 +178,12 @@
/**
* Taps an option in the save snackbar.
*
+ * @param type expected type of save info.
* @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
*/
- void saveForAutofill(boolean yesDoIt) {
- final UiObject2 saveSnackBar = assertSaveShowing();
- saveForAutofill(saveSnackBar, true);
+ void saveForAutofill(int type, boolean yesDoIt) {
+ final UiObject2 saveSnackBar = assertSaveShowing(type, null);
+ saveForAutofill(saveSnackBar, yesDoIt);
}
/**
diff --git a/tests/camera/Android.mk b/tests/camera/Android.mk
index 8ebca66..af7474e 100644
--- a/tests/camera/Android.mk
+++ b/tests/camera/Android.mk
@@ -62,7 +62,7 @@
LOCAL_PACKAGE_NAME := CtsCameraTestCases
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 965dc1c..d38ddd3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -37,6 +37,7 @@
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.OutputConfiguration;
import android.media.ImageReader;
import android.os.Handler;
import android.os.SystemClock;
@@ -667,6 +668,95 @@
}
/**
+ * Verify creating a custom session
+ */
+ public void testCreateCustomSession() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i], mCameraMockListener);
+ waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+ if (!mStaticInfo.isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIds[i] +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ testCreateCustomSessionByCamera(mCameraIds[i]);
+ }
+ finally {
+ closeDevice(mCameraIds[i], mCameraMockListener);
+ }
+ }
+ }
+
+ /**
+ * Verify creating a custom mode session works
+ */
+ private void testCreateCustomSessionByCamera(String cameraId) throws Exception {
+ final int SESSION_TIMEOUT_MS = 1000;
+ final int CAPTURE_TIMEOUT_MS = 3000;
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing creating custom session for camera " + cameraId);
+ }
+
+ Size yuvSize = mOrderedPreviewSizes.get(0);
+
+ // Create a list of image readers. JPEG for last one and YUV for the rest.
+ ImageReader imageReader = ImageReader.newInstance(yuvSize.getWidth(), yuvSize.getHeight(),
+ ImageFormat.YUV_420_888, /*maxImages*/1);
+
+ try {
+ // Create a normal-mode session via createCustomCaptureSession
+ mSessionMockListener = spy(new BlockingSessionCallback());
+ mSessionWaiter = mSessionMockListener.getStateWaiter();
+ List<OutputConfiguration> outputs = new ArrayList<>();
+ outputs.add(new OutputConfiguration(imageReader.getSurface()));
+ mCamera.createCustomCaptureSession(/*inputConfig*/null, outputs,
+ CameraDevice.SESSION_OPERATION_MODE_NORMAL, mSessionMockListener, mHandler);
+ mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+
+ // Verify we can capture a frame with the session.
+ SimpleCaptureCallback captureListener = new SimpleCaptureCallback();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+ imageReader.setOnImageAvailableListener(imageListener, mHandler);
+
+ CaptureRequest.Builder builder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ builder.addTarget(imageReader.getSurface());
+ CaptureRequest request = builder.build();
+
+ mSession.capture(request, captureListener, mHandler);
+ captureListener.getCaptureResultForRequest(request, CAPTURE_TIMEOUT_MS);
+ imageListener.getImage(CAPTURE_TIMEOUT_MS).close();
+
+ // Create a few invalid custom sessions by using undefined non-vendor mode indices, and
+ // check that they fail to configure
+ mSessionMockListener = spy(new BlockingSessionCallback());
+ mSessionWaiter = mSessionMockListener.getStateWaiter();
+ mCamera.createCustomCaptureSession(/*inputConfig*/null, outputs,
+ CameraDevice.SESSION_OPERATION_MODE_VENDOR_START - 1, mSessionMockListener, mHandler);
+ mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+ waitForSessionState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED,
+ SESSION_CONFIGURE_TIMEOUT_MS);
+
+ mSessionMockListener = spy(new BlockingSessionCallback());
+ mCamera.createCustomCaptureSession(/*inputConfig*/null, outputs,
+ CameraDevice.SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED + 1, mSessionMockListener,
+ mHandler);
+ mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+ mSessionWaiter = mSessionMockListener.getStateWaiter();
+ waitForSessionState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED,
+ SESSION_CONFIGURE_TIMEOUT_MS);
+
+ } finally {
+ imageReader.close();
+ mSession.close();
+ }
+ }
+
+
+ /**
* Verify creating sessions back to back and only the last one is valid for
* submitting requests.
*/
diff --git a/tests/fragment/AndroidManifest.xml b/tests/fragment/AndroidManifest.xml
index 05027b5..ecbd39b 100644
--- a/tests/fragment/AndroidManifest.xml
+++ b/tests/fragment/AndroidManifest.xml
@@ -27,6 +27,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity android:name=".LoaderActivity"/>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java b/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
index b1d7411..5547946 100644
--- a/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
+++ b/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
@@ -32,6 +32,9 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
public class FragmentTestUtil {
public static void waitForExecution(final ActivityTestRule<FragmentTestActivity> rule) {
// Wait for two cycles. When starting a postponed transition, it will post to
@@ -43,7 +46,7 @@
instrumentation.runOnMainSync(() -> {});
}
- private static void runOnUiThreadRethrow(ActivityTestRule<FragmentTestActivity> rule,
+ private static void runOnUiThreadRethrow(ActivityTestRule<? extends Activity> rule,
Runnable r) {
if (Looper.getMainLooper() == Looper.myLooper()) {
r.run();
@@ -57,12 +60,12 @@
}
public static boolean executePendingTransactions(
- final ActivityTestRule<FragmentTestActivity> rule) {
+ final ActivityTestRule<? extends Activity> rule) {
return executePendingTransactions(rule, rule.getActivity().getFragmentManager());
}
public static boolean executePendingTransactions(
- final ActivityTestRule<FragmentTestActivity> rule, final FragmentManager fm) {
+ final ActivityTestRule<? extends Activity> rule, final FragmentManager fm) {
final boolean[] ret = new boolean[1];
runOnUiThreadRethrow(rule, new Runnable() {
@Override
@@ -197,4 +200,21 @@
accessibilityNodeInfo.recycle();
return isVisible;
}
+
+ /**
+ * Allocates until a garbage collection occurs.
+ */
+ public static void forceGC() {
+ // Do it twice so that we know we're not in the middle of the first collection when
+ // returning.
+ for (int i = 0; i < 2; i++) {
+ // Use a random index in the list to detect the garbage collection each time because
+ // .get() may accidentally trigger a strong reference during collection.
+ ArrayList<WeakReference<byte[]>> leak = new ArrayList<>();
+ do {
+ WeakReference<byte[]> arr = new WeakReference<byte[]>(new byte[100]);
+ leak.add(arr);
+ } while (leak.get((int) (Math.random() * leak.size())).get() != null);
+ }
+ }
}
diff --git a/tests/fragment/src/android/fragment/cts/LoaderActivity.java b/tests/fragment/src/android/fragment/cts/LoaderActivity.java
new file mode 100644
index 0000000..bd5a1cc
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/LoaderActivity.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.fragment.cts;
+
+import android.app.Activity;
+import android.app.LoaderManager;
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.content.Loader;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * This Activity sets the text when loading completes. It also tracks the Activity in
+ * a static variable, so it must be cleared in test tear down.
+ */
+public class LoaderActivity extends Activity {
+ // These must be cleared after each test using clearState()
+ public static LoaderActivity sActivity;
+ public static CountDownLatch sResumed;
+
+ public TextView textView;
+
+ public static void clearState() {
+ sActivity = null;
+ sResumed = null;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sActivity = this;
+
+ setContentView(R.layout.text_a);
+ textView = (TextView) findViewById(R.id.textA);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ getLoaderManager().initLoader(0, null, new TextLoaderCallback());
+ if (sResumed != null) {
+ sResumed.countDown();
+ }
+ }
+
+ class TextLoaderCallback implements LoaderManager.LoaderCallbacks<String> {
+ @Override
+ public Loader<String> onCreateLoader(int id, Bundle args) {
+ return new TextLoader(LoaderActivity.this);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<String> loader, String data) {
+ textView.setText(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<String> loader) {
+ }
+ }
+
+ static class TextLoader extends AsyncTaskLoader<String> {
+ TextLoader(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ forceLoad();
+ }
+
+ @Override
+ public String loadInBackground() {
+ return "Loaded!";
+ }
+ }
+}
diff --git a/tests/fragment/src/android/fragment/cts/LoaderTest.java b/tests/fragment/src/android/fragment/cts/LoaderTest.java
new file mode 100755
index 0000000..9e3097c
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/LoaderTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.fragment.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.Instrumentation;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class LoaderTest {
+ @Rule
+ public ActivityTestRule<LoaderActivity> mActivityRule =
+ new ActivityTestRule<>(LoaderActivity.class);
+
+ @After
+ public void clearActivity() {
+ LoaderActivity.clearState();
+ }
+
+ /**
+ * Test to ensure that there is no Activity leak due to Loader
+ */
+ @Test
+ public void testLeak() throws Throwable {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent(mActivityRule.getActivity(), LoaderActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ LoaderActivity.sResumed = new CountDownLatch(1);
+ instrumentation.startActivitySync(intent);
+ assertTrue(LoaderActivity.sResumed.await(1, TimeUnit.SECONDS));
+
+ LoaderFragment fragment = new LoaderFragment();
+ FragmentManager fm = LoaderActivity.sActivity.getFragmentManager();
+
+ fm.beginTransaction()
+ .add(fragment, "1")
+ .commit();
+
+ FragmentTestUtil.executePendingTransactions(mActivityRule, fm);
+
+ fm.beginTransaction()
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit();
+
+ FragmentTestUtil.executePendingTransactions(mActivityRule, fm);
+
+ WeakReference<LoaderActivity> weakActivity = new WeakReference(LoaderActivity.sActivity);
+
+ if (!switchOrientation()) {
+ return; // can't switch orientation for square screens
+ }
+
+ // Now force a garbage collection.
+ FragmentTestUtil.forceGC();
+ assertNull(weakActivity.get());
+ }
+
+ /**
+ * When a LoaderManager is reused, it should notify in onResume
+ */
+ @Test
+ public void startWhenReused() throws Throwable {
+ LoaderActivity activity = mActivityRule.getActivity();
+
+ assertEquals("Loaded!", activity.textView.getText().toString());
+
+ if (!switchOrientation()) {
+ return; // can't switch orientation for square screens
+ }
+
+ // After orientation change, the text should still be loaded properly
+ activity = LoaderActivity.sActivity;
+ assertEquals("Loaded!", activity.textView.getText().toString());
+ }
+
+ private boolean switchOrientation() throws InterruptedException {
+ LoaderActivity activity = LoaderActivity.sActivity;
+
+ int currentOrientation = activity.getResources().getConfiguration().orientation;
+
+ int nextOrientation;
+ if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+ nextOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ } else if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
+ nextOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ } else {
+ return false; // Don't know what to do with square or unknown orientations
+ }
+
+ // Now switch the orientation
+ LoaderActivity.sResumed = new CountDownLatch(1);
+
+ activity.setRequestedOrientation(nextOrientation);
+ assertTrue(LoaderActivity.sResumed.await(1, TimeUnit.SECONDS));
+ return true;
+ }
+
+
+ public static class LoaderFragment extends Fragment {
+ private static final int LOADER_ID = 1;
+ private final LoaderManager.LoaderCallbacks<Boolean> mLoaderCallbacks =
+ new LoaderManager.LoaderCallbacks<Boolean>() {
+ @Override
+ public Loader<Boolean> onCreateLoader(int id, Bundle args) {
+ return new DummyLoader(getContext());
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Boolean> loader, Boolean data) {
+
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Boolean> loader) {
+
+ }
+ };
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ getLoaderManager().initLoader(LOADER_ID, null, mLoaderCallbacks);
+ }
+ }
+
+ static class DummyLoader extends Loader<Boolean> {
+ DummyLoader(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ deliverResult(true);
+ }
+ }
+}
diff --git a/tests/sensor/Android.mk b/tests/sensor/Android.mk
index 08f88af..d275b1f 100644
--- a/tests/sensor/Android.mk
+++ b/tests/sensor/Android.mk
@@ -48,8 +48,10 @@
LOCAL_SRC_FILES := \
jni/SensorTest.cpp \
- jni/android_view_cts_SensorNativeTest.cpp \
- jni/nativeTestHelper.cpp \
+ jni/SensorTestCases.cpp \
+ jni/android_hardware_cts_SensorDirectReportTest.cpp \
+ jni/android_hardware_cts_SensorNativeTest.cpp \
+ jni/nativeTestHelper.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
diff --git a/tests/sensor/jni/SensorTest.cpp b/tests/sensor/jni/SensorTest.cpp
index 3593047..eb2bfe8 100644
--- a/tests/sensor/jni/SensorTest.cpp
+++ b/tests/sensor/jni/SensorTest.cpp
@@ -35,10 +35,6 @@
}
}
-void SensorTest::testInitialized(JNIEnv *env) {
- ASSERT_TRUE(mManager->isValid());
-}
-
TestSensorManager::TestSensorManager(const char *package) {
mManager = ASensorManager_getInstanceForPackage(package);
}
@@ -162,6 +158,30 @@
success = true;
break;
}
+ case ASENSOR_DIRECT_CHANNEL_TYPE_HARDWARE_BUFFER: {
+ AHardwareBuffer_Desc desc = {
+ .width = static_cast<uint32_t>(size),
+ .height = 1,
+ .layers = 1,
+ .usage0 = AHARDWAREBUFFER_USAGE0_SENSOR_DIRECT_DATA
+ | AHARDWAREBUFFER_USAGE0_CPU_READ_OFTEN,
+ .usage1 = 0,
+ .format = AHARDWAREBUFFER_FORMAT_BLOB
+ };
+
+ // allocate
+ if (AHardwareBuffer_allocate(&desc, &mHardwareBuffer) == 0) {
+ // lock
+ if (AHardwareBuffer_lock(mHardwareBuffer, AHARDWAREBUFFER_USAGE0_CPU_READ,
+ -1, nullptr, reinterpret_cast<void **>(&mBuffer)) == 0) {
+ if (mBuffer != nullptr) {
+ mSize = size;
+ success = true;
+ }
+ }
+ }
+ break;
+ }
default:
break;
}
@@ -189,10 +209,23 @@
mSize = 0;
break;
}
+ case ASENSOR_DIRECT_CHANNEL_TYPE_HARDWARE_BUFFER: {
+ if (mHardwareBuffer != nullptr) {
+ if (mBuffer != nullptr) {
+ int32_t fence = -1;
+ AHardwareBuffer_unlock(mHardwareBuffer, &fence);
+ mBuffer = nullptr;
+ }
+ AHardwareBuffer_release(mHardwareBuffer);
+ mHardwareBuffer = nullptr;
+ }
+ mSize = 0;
+ break;
+ }
default:
break;
}
- if (mSharedMemoryFd > 0 || mSize != 0 || mBuffer != nullptr) {
+ if (mSharedMemoryFd > 0 || mSize != 0 || mBuffer != nullptr || mHardwareBuffer != nullptr) {
ALOGE("TestSharedMemory %p not properly destructed: "
"type %d, shared_memory_fd %d, hardware_buffer %p, size %zu, buffer %p",
this, static_cast<int>(mType), mSharedMemoryFd, mHardwareBuffer, mSize, mBuffer);
@@ -212,69 +245,5 @@
}
return m;
}
-
-// Test direct report of gyroscope at normal rate level through ashmem direct channel
-void SensorTest::testGyroscopeSharedMemoryDirectReport(JNIEnv* env) {
- constexpr int type = ASENSOR_TYPE_GYROSCOPE;
- constexpr size_t kEventSize = sizeof(ASensorEvent);
- constexpr size_t kNEvent = 500;
- constexpr size_t kMemSize = kEventSize * kNEvent;
-
- TestSensor sensor = mManager->getDefaultSensor(type);
-
- if (sensor.getHighestDirectReportRateLevel() == ASENSOR_DIRECT_RATE_STOP
- || !sensor.isDirectChannelTypeSupported(ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY)) {
- // does not declare support
- return;
- }
-
- std::unique_ptr<TestSharedMemory>
- mem(TestSharedMemory::create(ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY, kMemSize));
- ASSERT_NE(mem, nullptr);
- ASSERT_NE(mem->getBuffer(), nullptr);
- ASSERT_GT(mem->getSharedMemoryFd(), 0);
-
- char* buffer = mem->getBuffer();
- // fill memory with data
- for (size_t i = 0; i < kMemSize; ++i) {
- buffer[i] = '\xcc';
- }
-
- int32_t channel;
- channel = mManager->createDirectChannel(*mem);
- ASSERT_GT(channel, 0);
-
- // check memory is zeroed
- for (size_t i = 0; i < kMemSize; ++i) {
- ASSERT_EQ(buffer[i], '\0');
- }
-
- int32_t eventToken;
- eventToken = mManager->configureDirectReport(sensor, channel, ASENSOR_DIRECT_RATE_NORMAL);
- usleep(1500000); // sleep 1 sec for data, plus 0.5 sec for initialization
- auto events = mem->parseEvents();
-
- // allowed to be between 55% and 220% of nominal freq (50Hz)
- ASSERT_GT(events.size(), 50 / 2);
- ASSERT_LT(events.size(), static_cast<size_t>(110*1.5));
-
- int64_t lastTimestamp = 0;
- for (auto &e : events) {
- ASSERT_EQ(e.type, type);
- ASSERT_EQ(e.sensor, eventToken);
- ASSERT_GT(e.timestamp, lastTimestamp);
-
- ASensorVector &gyro = e.vector;
- double gyroNorm = std::sqrt(gyro.x * gyro.x + gyro.y * gyro.y + gyro.z * gyro.z);
- // assert not drifting
- ASSERT_TRUE(gyroNorm < 0.1); // < ~5 degree/sa
-
- lastTimestamp = e.timestamp;
- }
-
- // stop sensor and unregister channel
- mManager->configureDirectReport(sensor, channel, ASENSOR_DIRECT_RATE_STOP);
- mManager->destroyDirectChannel(channel);
-}
} // namespace SensorTest
} // namespace android
diff --git a/tests/sensor/jni/SensorTest.h b/tests/sensor/jni/SensorTest.h
index 5f8d703..7abd1e9 100644
--- a/tests/sensor/jni/SensorTest.h
+++ b/tests/sensor/jni/SensorTest.h
@@ -42,7 +42,9 @@
// tests
void testInitialized(JNIEnv *env);
- void testGyroscopeSharedMemoryDirectReport(JNIEnv *env);
+ void testInvalidParameter(JNIEnv *env);
+ void testDirectReport(JNIEnv *env, int32_t sensorType, int32_t channelType, int32_t rateLevel);
+
private:
std::unique_ptr<TestSensorManager> mManager;
};
diff --git a/tests/sensor/jni/SensorTestCases.cpp b/tests/sensor/jni/SensorTestCases.cpp
new file mode 100644
index 0000000..713eabf
--- /dev/null
+++ b/tests/sensor/jni/SensorTestCases.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SensorTest.h"
+#include <errno.h>
+
+namespace android {
+namespace SensorTest {
+
+// Test if test environment is correctly initialized
+void SensorTest::testInitialized(JNIEnv *env) {
+ ASSERT_TRUE(mManager->isValid());
+}
+
+// Test if invalid parameter cases are handled correctly
+void SensorTest::testInvalidParameter(JNIEnv *env) {
+ ASensorList dummyList;
+ ASSERT_EQ(ASensorManager_getSensorList(nullptr, nullptr), -EINVAL);
+ ASSERT_EQ(ASensorManager_getSensorList(nullptr, &dummyList), -EINVAL);
+
+ ASSERT_EQ(ASensorManager_getDefaultSensor(nullptr, ASENSOR_TYPE_ACCELEROMETER), nullptr);
+
+ ASSERT_EQ(ASensorManager_getDefaultSensorEx(
+ nullptr, ASENSOR_TYPE_ACCELEROMETER, false), nullptr);
+
+ ALooper *nonNullLooper = reinterpret_cast<ALooper *>(1);
+ ASensorManager *nonNullManager = reinterpret_cast<ASensorManager *>(1);
+ ASSERT_EQ(ASensorManager_createEventQueue(nullptr, nullptr, 0, nullptr, nullptr), nullptr);
+ ASSERT_EQ(ASensorManager_createEventQueue(
+ nullptr, nonNullLooper, 0, nullptr, nullptr), nullptr);
+ ASSERT_EQ(ASensorManager_createEventQueue(
+ nonNullManager, nullptr, 0, nullptr, nullptr), nullptr);
+
+ ASensorEventQueue *nonNullQueue = reinterpret_cast<ASensorEventQueue *>(1);
+ ASSERT_EQ(ASensorManager_destroyEventQueue(nullptr, nullptr), -EINVAL);
+ ASSERT_EQ(ASensorManager_destroyEventQueue(nullptr, nonNullQueue), -EINVAL);
+ ASSERT_EQ(ASensorManager_destroyEventQueue(nonNullManager, nullptr), -EINVAL);
+
+ int fakeValidFd = 1;
+ int invalidFd = -1;
+ ASSERT_EQ(ASensorManager_createSharedMemoryDirectChannel(
+ nullptr, fakeValidFd, sizeof(ASensorEvent)), -EINVAL);
+ ASSERT_EQ(ASensorManager_createSharedMemoryDirectChannel(
+ nonNullManager, invalidFd, sizeof(ASensorEvent)), -EINVAL);
+ ASSERT_EQ(ASensorManager_createSharedMemoryDirectChannel(
+ nonNullManager, fakeValidFd, sizeof(ASensorEvent) - 1), -EINVAL);
+ ASSERT_EQ(ASensorManager_createSharedMemoryDirectChannel(
+ nonNullManager, fakeValidFd, 0), -EINVAL);
+
+ AHardwareBuffer *nonNullHardwareBuffer = reinterpret_cast<AHardwareBuffer *>(1);
+ ASSERT_EQ(ASensorManager_createHardwareBufferDirectChannel(
+ nullptr, nonNullHardwareBuffer, sizeof(ASensorEvent)), -EINVAL);
+ ASSERT_EQ(ASensorManager_createHardwareBufferDirectChannel(
+ nonNullManager, nullptr, sizeof(ASensorEvent)), -EINVAL);
+ ASSERT_EQ(ASensorManager_createHardwareBufferDirectChannel(
+ nonNullManager, nonNullHardwareBuffer, sizeof(ASensorEvent) - 1), -EINVAL);
+ ASSERT_EQ(ASensorManager_createHardwareBufferDirectChannel(
+ nonNullManager, nonNullHardwareBuffer, 0), -EINVAL);
+
+ // no return value to test, but call this to test if it will crash
+ ASensorManager_destroyDirectChannel(nullptr, 1);
+
+ ASensor *nonNullSensor = reinterpret_cast<ASensor *>(1);
+ ASSERT_EQ(ASensorManager_configureDirectReport(
+ nullptr, nullptr, 1, ASENSOR_DIRECT_RATE_NORMAL), -EINVAL);
+ ASSERT_EQ(ASensorManager_configureDirectReport(
+ nullptr, nonNullSensor, 1, ASENSOR_DIRECT_RATE_NORMAL), -EINVAL);
+ ASSERT_EQ(ASensorManager_configureDirectReport(
+ nullptr, nonNullSensor, 1, ASENSOR_DIRECT_RATE_STOP), -EINVAL);
+ ASSERT_EQ(ASensorManager_configureDirectReport(
+ nonNullManager, nullptr, 1, ASENSOR_DIRECT_RATE_NORMAL), -EINVAL);
+
+ ASSERT_EQ(ASensorEventQueue_registerSensor(nullptr, nullptr, 1, 1), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_registerSensor(nullptr, nonNullSensor, 1, 1), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_registerSensor(nonNullQueue, nullptr, 1, 1), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_registerSensor(nonNullQueue, nonNullSensor, -1, 1), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_registerSensor(nonNullQueue, nonNullSensor, 1, -1), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_registerSensor(nonNullQueue, nonNullSensor, -1, -1), -EINVAL);
+
+ ASSERT_EQ(ASensorEventQueue_enableSensor(nullptr, nullptr), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_enableSensor(nullptr, nonNullSensor), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_enableSensor(nonNullQueue, nullptr), -EINVAL);
+
+ ASSERT_EQ(ASensorEventQueue_disableSensor(nullptr, nullptr), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_disableSensor(nullptr, nonNullSensor), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_disableSensor(nonNullQueue, nullptr), -EINVAL);
+
+ ASSERT_EQ(ASensorEventQueue_setEventRate(nullptr, nullptr, 1), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_setEventRate(nullptr, nonNullSensor, 1), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_setEventRate(nonNullQueue, nullptr, 1), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_setEventRate(nonNullQueue, nonNullSensor, -1), -EINVAL);
+
+ ASSERT_EQ(ASensorEventQueue_hasEvents(nullptr), -EINVAL);
+
+ ASensorEvent event;
+ ASensorEvent *nonNullEvent = &event;
+ ASSERT_EQ(ASensorEventQueue_getEvents(nullptr, nullptr, 1), -EINVAL)
+ ASSERT_EQ(ASensorEventQueue_getEvents(nullptr, nullptr, 0), -EINVAL)
+ ASSERT_EQ(ASensorEventQueue_getEvents(nullptr, nonNullEvent, 1), -EINVAL)
+ ASSERT_EQ(ASensorEventQueue_getEvents(nullptr, nonNullEvent, 0), -EINVAL);
+ ASSERT_EQ(ASensorEventQueue_getEvents(nonNullQueue, nullptr, 1), -EINVAL)
+ ASSERT_EQ(ASensorEventQueue_getEvents(nonNullQueue, nullptr, 0), -EINVAL);
+
+ ASSERT_EMPTY_CSTR(ASensor_getName(nullptr));
+ ASSERT_EMPTY_CSTR(ASensor_getVendor(nullptr));
+ ASSERT_EQ(ASensor_getType(nullptr), -1);
+ ASSERT_EQ(ASensor_getResolution(nullptr), -1.f);
+ ASSERT_EQ(ASensor_getMinDelay(nullptr), -1);
+ ASSERT_EQ(ASensor_getFifoMaxEventCount(nullptr), -1);
+ ASSERT_EQ(ASensor_getFifoReservedEventCount(nullptr), -1);
+ ASSERT_EMPTY_CSTR(ASensor_getStringType(nullptr));
+ ASSERT_EQ(ASensor_getReportingMode(nullptr), -1);
+ ASSERT_EQ(ASensor_isWakeUpSensor(nullptr), false);
+ ASSERT_EQ(ASensor_isDirectChannelTypeSupported(
+ nullptr, ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY), false);
+ ASSERT_EQ(ASensor_isDirectChannelTypeSupported(
+ nullptr, ASENSOR_DIRECT_CHANNEL_TYPE_HARDWARE_BUFFER), false);
+ ASSERT_EQ(ASensor_getHighestDirectReportRateLevel(nullptr), ASENSOR_DIRECT_RATE_STOP);
+}
+
+// Test sensor direct report functionality
+void SensorTest::testDirectReport(JNIEnv* env, int32_t sensorType, int32_t channelType, int32_t rateLevel) {
+ constexpr size_t kEventSize = sizeof(ASensorEvent);
+ constexpr size_t kNEvent = 500;
+ constexpr size_t kMemSize = kEventSize * kNEvent;
+
+ // value check criterion
+ constexpr float GRAVITY_MIN = 9.81f - 0.5f;
+ constexpr float GRAVITY_MAX = 9.81f + 0.5f;
+ constexpr float GYRO_MAX = 0.1f; // ~5 dps
+
+ constexpr float RATE_NORMAL_NOMINAL = 50;
+ constexpr float RATE_FAST_NOMINAL = 200;
+ constexpr float RATE_VERY_FAST_NOMINAL = 800;
+
+ TestSensor sensor = mManager->getDefaultSensor(sensorType);
+ if (!sensor.isValid()
+ || sensor.getHighestDirectReportRateLevel() < rateLevel
+ || !sensor.isDirectChannelTypeSupported(channelType)) {
+ // no sensor of type sensorType or it does not declare support of channelType or rateLevel
+ return;
+ }
+
+ std::unique_ptr<TestSharedMemory> mem(TestSharedMemory::create(channelType, kMemSize));
+ ASSERT_NE(mem, nullptr);
+ ASSERT_NE(mem->getBuffer(), nullptr);
+ switch (channelType) {
+ case ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY:
+ ASSERT_GT(mem->getSharedMemoryFd(), 0);
+ break;
+ case ASENSOR_DIRECT_CHANNEL_TYPE_HARDWARE_BUFFER:
+ ASSERT_NOT_NULL(mem->getHardwareBuffer());
+ break;
+ }
+
+ char* buffer = mem->getBuffer();
+ // fill memory with data
+ for (size_t i = 0; i < kMemSize; ++i) {
+ buffer[i] = '\xcc';
+ }
+
+ int32_t channel;
+ channel = mManager->createDirectChannel(*mem);
+ ASSERT_GT(channel, 0);
+
+ // check memory is zeroed
+ for (size_t i = 0; i < kMemSize; ++i) {
+ ASSERT_EQ(buffer[i], '\0');
+ }
+
+ int32_t eventToken;
+ eventToken = mManager->configureDirectReport(sensor, channel, rateLevel);
+ usleep(1500000); // sleep 1 sec for data, plus 0.5 sec for initialization
+ auto events = mem->parseEvents();
+
+ // find norminal rate
+ float nominalFreq = 0.f;
+ float nominalTestTimeSec = 1.f;
+ float maxTestTimeSec = 1.5f;
+ switch (rateLevel) {
+ case ASENSOR_DIRECT_RATE_NORMAL:
+ nominalFreq = RATE_NORMAL_NOMINAL;
+ break;
+ case ASENSOR_DIRECT_RATE_FAST:
+ nominalFreq = RATE_FAST_NOMINAL;
+ break;
+ case ASENSOR_DIRECT_RATE_VERY_FAST:
+ nominalFreq = RATE_VERY_FAST_NOMINAL;
+ break;
+ }
+
+ // allowed to be between 55% and 220% of nominal freq
+ ASSERT_GT(events.size(), static_cast<size_t>(nominalFreq * 0.55f * nominalTestTimeSec));
+ ASSERT_LT(events.size(), static_cast<size_t>(nominalFreq * 2.2f * maxTestTimeSec));
+
+ int64_t lastTimestamp = 0;
+ for (auto &e : events) {
+ ASSERT_EQ(e.type, sensorType);
+ ASSERT_EQ(e.sensor, eventToken);
+ ASSERT_GT(e.timestamp, lastTimestamp);
+
+ // type specific value check
+ switch(sensorType) {
+ case ASENSOR_TYPE_ACCELEROMETER: {
+ ASensorVector &acc = e.vector;
+ double accNorm = std::sqrt(acc.x * acc.x + acc.y * acc.y + acc.z * acc.z);
+ if (accNorm > GRAVITY_MAX || accNorm < GRAVITY_MIN) {
+ ALOGE("Gravity norm = %f", accNorm);
+ }
+ ASSERT_GE(accNorm, GRAVITY_MIN);
+ ASSERT_LE(accNorm, GRAVITY_MAX);
+ break;
+ }
+ case ASENSOR_TYPE_GYROSCOPE: {
+ ASensorVector &gyro = e.vector;
+ double gyroNorm = std::sqrt(gyro.x * gyro.x + gyro.y * gyro.y + gyro.z * gyro.z);
+ // assert not drifting
+ ASSERT_LE(gyroNorm, GYRO_MAX); // < ~2.5 degree/s
+ break;
+ }
+ }
+
+ lastTimestamp = e.timestamp;
+ }
+
+ // stop sensor and unregister channel
+ mManager->configureDirectReport(sensor, channel, ASENSOR_DIRECT_RATE_STOP);
+ mManager->destroyDirectChannel(channel);
+}
+} // namespace SensorTest
+} // namespace android
diff --git a/tests/sensor/jni/android_hardware_cts_SensorDirectReportTest.cpp b/tests/sensor/jni/android_hardware_cts_SensorDirectReportTest.cpp
new file mode 100644
index 0000000..2616ff9
--- /dev/null
+++ b/tests/sensor/jni/android_hardware_cts_SensorDirectReportTest.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"nclude
+ * <android/hardware_buffer_jni.h>);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "nativeTestHelper.h"
+#include "SensorTest.h"
+
+#include <android/hardware_buffer_jni.h>
+
+namespace {
+jboolean readHardwareBuffer(JNIEnv* env, jclass,
+ jobject hardwareBufferObj, jbyteArray buffer, jint srcOffset, jint destOffset, jint count) {
+ if (hardwareBufferObj == nullptr || buffer == nullptr ||
+ srcOffset < 0 || destOffset < 0 || count <= 0) {
+ return false;
+ }
+
+ if (env->GetArrayLength(buffer) < destOffset + count) {
+ ALOGE("Byte array is not large enough.");
+ return false;
+ }
+
+ AHardwareBuffer *hardwareBuffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBufferObj);
+ if (hardwareBuffer == nullptr) {
+ ALOGE("Cannot get AHardwareBuffer from HardwareBuffer");
+ return false;
+ }
+
+ void *address;
+ int32_t fence = -1;
+ jboolean ret = false;
+ if (AHardwareBuffer_lock(hardwareBuffer, AHARDWAREBUFFER_USAGE0_CPU_READ,
+ fence, nullptr, &address) == 0) {
+ if (address != nullptr) {
+ env->SetByteArrayRegion(
+ buffer, destOffset, count, reinterpret_cast<const jbyte *>(address));
+ ret = true;
+ } else {
+ ALOGE("AHardwareBuffer locked but address is invalid");
+ }
+ AHardwareBuffer_unlock(hardwareBuffer, &fence);
+ }
+ return ret;
+}
+
+JNINativeMethod gMethods[] = {
+ { "nativeReadHardwareBuffer", "(Landroid/hardware/HardwareBuffer;[BIII)Z",
+ (void *) readHardwareBuffer},
+};
+} // unamed namespace
+
+int register_android_hardware_cts_SensorDirectReportTest(JNIEnv* env) {
+ jclass clazz = env->FindClass("android/hardware/cts/SensorDirectReportTest");
+ return env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/sensor/jni/android_hardware_cts_SensorNativeTest.cpp b/tests/sensor/jni/android_hardware_cts_SensorNativeTest.cpp
new file mode 100644
index 0000000..40a5ff7
--- /dev/null
+++ b/tests/sensor/jni/android_hardware_cts_SensorNativeTest.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "nativeTestHelper.h"
+#include "SensorTest.h"
+
+namespace {
+using android::SensorTest::SensorTest;
+
+#define RETURN_ON_EXCEPTION() do { if (env->ExceptionCheck()) { return;} } while(false)
+
+jlong setUp(JNIEnv*, jclass) {
+ SensorTest *test = new SensorTest();
+ if (test != nullptr) {
+ test->SetUp();
+ }
+ return reinterpret_cast<jlong>(test);
+}
+
+void tearDown(JNIEnv*, jclass, jlong instance) {
+ delete reinterpret_cast<SensorTest *>(instance);
+}
+
+void test(JNIEnv* env, jclass, jlong instance) {
+ SensorTest *test = reinterpret_cast<SensorTest *>(instance);
+ ASSERT_NOT_NULL(test);
+
+ // test if SensorTest is intialized
+ ALOGI("testInitialized");
+ test->testInitialized(env);
+ RETURN_ON_EXCEPTION();
+
+ // test if SensorTest is intialized
+ ALOGI("testInvalidParameter");
+ test->testInvalidParameter(env);
+ RETURN_ON_EXCEPTION();
+
+ // test sensor direct report
+ std::vector<int32_t> sensorTypes ={ASENSOR_TYPE_ACCELEROMETER, ASENSOR_TYPE_GYROSCOPE};
+ std::vector<int32_t> rates = {
+ ASENSOR_DIRECT_RATE_NORMAL, ASENSOR_DIRECT_RATE_FAST, ASENSOR_DIRECT_RATE_VERY_FAST};
+ std::vector<int32_t> channelTypes =
+ {ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY, ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY};
+ for (auto s : sensorTypes) {
+ for (auto c : channelTypes) {
+ for (auto r : rates) {
+ ALOGI("testDirectReport: sensorType = %d, channelType = %d, ratelevel = %d",
+ s, c, r);
+ test->testDirectReport(env, s, c, r);
+ RETURN_ON_EXCEPTION();
+ }
+ }
+ }
+}
+
+JNINativeMethod gMethods[] = {
+ { "nativeSetUp", "()J",
+ (void *) setUp},
+ { "nativeTearDown", "(J)V",
+ (void *) tearDown},
+ { "nativeTest", "(J)V",
+ (void *) test},
+};
+} // unamed namespace
+
+int register_android_hardware_cts_SensorNativeTest(JNIEnv* env) {
+ jclass clazz = env->FindClass("android/hardware/cts/SensorNativeTest");
+ return env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/sensor/jni/android_view_cts_SensorNativeTest.cpp b/tests/sensor/jni/android_view_cts_SensorNativeTest.cpp
deleted file mode 100644
index 0c67928..0000000
--- a/tests/sensor/jni/android_view_cts_SensorNativeTest.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#include "nativeTestHelper.h"
-#include "SensorTest.h"
-
-namespace {
-using android::SensorTest::SensorTest;
-
-jlong setUp(JNIEnv*, jclass) {
- SensorTest *test = new SensorTest();
- if (test != nullptr) {
- test->SetUp();
- }
- return reinterpret_cast<jlong>(test);
-}
-
-void tearDown(JNIEnv*, jclass, jlong instance) {
- delete reinterpret_cast<SensorTest *>(instance);
-}
-
-void test(JNIEnv* env, jclass, jlong instance) {
- SensorTest *test = reinterpret_cast<SensorTest *>(instance);
- ASSERT_NE(test, nullptr);
-
- // test if SensorTest is intialized
- test->testInitialized(env);
-
- // test gyro direct report using shared memory buffer
- test->testGyroscopeSharedMemoryDirectReport(env);
-}
-
-JNINativeMethod gMethods[] = {
- { "nativeSetUp", "()J",
- (void *) setUp},
- { "nativeTearDown", "(J)V",
- (void *) tearDown},
- { "nativeTest", "(J)V",
- (void *) test},
-};
-} // unamed namespace
-
-int register_android_view_cts_SensorNativeTest(JNIEnv* env)
-{
- jclass clazz = env->FindClass("android/hardware/cts/SensorNativeTest");
- return env->RegisterNatives(clazz, gMethods,
- sizeof(gMethods) / sizeof(JNINativeMethod));
-}
diff --git a/tests/sensor/jni/nativeTestHelper.cpp b/tests/sensor/jni/nativeTestHelper.cpp
index 7c1b378..3c7df9a 100644
--- a/tests/sensor/jni/nativeTestHelper.cpp
+++ b/tests/sensor/jni/nativeTestHelper.cpp
@@ -19,7 +19,8 @@
#include <cstdlib>
#include <cstring>
-extern int register_android_view_cts_SensorNativeTest(JNIEnv* env);
+extern int register_android_hardware_cts_SensorNativeTest(JNIEnv* env);
+extern int register_android_hardware_cts_SensorDirectReportTest(JNIEnv* env);
void fail(JNIEnv* env, const char* format, ...) {
va_list args;
@@ -41,7 +42,10 @@
if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
return JNI_ERR;
}
- if (register_android_view_cts_SensorNativeTest(env)) {
+ if (register_android_hardware_cts_SensorNativeTest(env)) {
+ return JNI_ERR;
+ }
+ if (register_android_hardware_cts_SensorDirectReportTest(env)) {
return JNI_ERR;
}
return JNI_VERSION_1_4;
diff --git a/tests/sensor/jni/nativeTestHelper.h b/tests/sensor/jni/nativeTestHelper.h
index cf7f1bd..e465667 100644
--- a/tests/sensor/jni/nativeTestHelper.h
+++ b/tests/sensor/jni/nativeTestHelper.h
@@ -44,5 +44,17 @@
ASSERT((a) < (b), "assert failed on (" #a " < " #b ") at " __FILE__ ":%d", __LINE__)
#define ASSERT_LE(a, b) \
ASSERT((a) <= (b), "assert failed on (" #a " <= " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_NOT_NULL(a) \
+ ASSERT((a) != nullptr, "assert failed on isNotNull(" #a ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_EMPTY_CSTR(a) do { \
+ const char *tmp = a; \
+ ASSERT(tmp != nullptr, \
+ "assert failed on (empty_cstr(" #a "): " #a " != nullptr) " \
+ "at " __FILE__ ":%d", __LINE__); \
+ ASSERT(tmp[0] == '\0', \
+ "assert failed on (empty_cstr(" #a "): strlen() == 0) " \
+ "at " __FILE__ ":%d", __LINE__); \
+ } while (false)
+
void fail(JNIEnv* env, const char* format, ...);
diff --git a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
index edc71aa..547f44c 100644
--- a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
@@ -57,9 +57,9 @@
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;
+ private static final float GRAVITY_MIN = 9.81f - 0.5f;
+ private static final float GRAVITY_MAX = 9.81f + 0.5f;
+ private static final float GYRO_NORM_MAX = 0.1f;
// test constants
private static final int REST_PERIOD_BEFORE_TEST_MILLISEC = 3000;
@@ -69,6 +69,9 @@
private static final int SHARED_MEMORY_SIZE = 2000 * SENSORS_EVENT_SIZE;
private static final float MERCY_FACTOR = 0.1f;
+ private static native boolean nativeReadHardwareBuffer(HardwareBuffer hardwareBuffer,
+ byte[] buffer, int srcOffset, int destOffset, int count);
+
private boolean mNeedMemoryFile;
private MemoryFile mMemoryFile;
private boolean mNeedHardwareBuffer;
@@ -78,6 +81,10 @@
private SensorManager mSensorManager;
private SensorDirectChannel mChannel;
+ static {
+ System.loadLibrary("cts-sensors-ndk-jni");
+ }
+
@Override
protected void setUp() throws Exception {
mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
@@ -323,11 +330,9 @@
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
+ SHARED_MEMORY_SIZE, 1 /* height */, HardwareBuffer.BLOB, 1 /* layer */,
+ HardwareBuffer.USAGE0_CPU_READ_OFTEN | HardwareBuffer.USAGE0_GPU_DATA_BUFFER
| HardwareBuffer.USAGE0_SENSOR_DIRECT_DATA);
return hardwareBuffer;
}
@@ -345,11 +350,14 @@
private boolean isSharedMemoryFormatted(int memType) {
if (memType == SensorDirectChannel.TYPE_ASHMEM) {
if (!readMemoryFileContent()) {
+ Log.e(TAG, "Read MemoryFile content fail");
return false;
}
} else {
- // Android API only support memory file read back
- return true;
+ if (!readHardwareBufferContent()) {
+ Log.e(TAG, "Read HardwareBuffer content fail");
+ return false;
+ }
}
for (byte b : mBuffer) {
@@ -364,8 +372,7 @@
if (memType == SensorDirectChannel.TYPE_ASHMEM) {
assertTrue("read MemoryFile content failed", readMemoryFileContent());
} else {
- // Android API only support memory file read back
- return;
+ assertTrue("read HardwareBuffer content failed", readHardwareBufferContent());
}
int offset = 0;
@@ -455,6 +462,10 @@
return true;
}
+ private boolean readHardwareBufferContent() {
+ return nativeReadHardwareBuffer(mHardwareBuffer, mBuffer, 0, 0, SHARED_MEMORY_SIZE);
+ }
+
private class DirectReportSensorEvent {
int size;
int token;
diff --git a/tests/signature/src/android/signature/cts/FailureType.java b/tests/signature/src/android/signature/cts/FailureType.java
index 5aaebc4..a701202 100644
--- a/tests/signature/src/android/signature/cts/FailureType.java
+++ b/tests/signature/src/android/signature/cts/FailureType.java
@@ -10,6 +10,7 @@
MISSING_FIELD,
MISMATCH_CLASS,
MISMATCH_INTERFACE,
+ MISMATCH_INTERFACE_METHOD,
MISMATCH_METHOD,
MISMATCH_FIELD,
CAUGHT_EXCEPTION,
diff --git a/tests/signature/src/android/signature/cts/JDiffClassDescription.java b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
index 35e97d0..2779ea7 100644
--- a/tests/signature/src/android/signature/cts/JDiffClassDescription.java
+++ b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
@@ -51,6 +51,23 @@
/** Indicates that the method is a synthetic method. */
private static final int METHOD_MODIFIER_SYNTHETIC = 0x00001000;
+ private static final Set<String> HIDDEN_INTERFACE_WHITELIST = new HashSet<>();
+ static {
+ // Interfaces that define @hide methods will by definition contain
+ // methods that do not appear in current.txt. Interfaces added to this
+ // list are probably not meant to be implemented in an application.
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract boolean android.companion.DeviceFilter.matches(D)");
+ HIDDEN_INTERFACE_WHITELIST.add("public static <D> boolean android.companion.DeviceFilter.matches(android.companion.DeviceFilter<D>,D)");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.nfc.tech.TagTechnology.reconnect() throws java.io.IOException");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.os.IBinder.shellCommand(java.io.FileDescriptor,java.io.FileDescriptor,java.io.FileDescriptor,java.lang.String[],android.os.ShellCallback,android.os.ResultReceiver) throws android.os.RemoteException");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract int android.text.ParcelableSpan.getSpanTypeIdInternal()");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.text.ParcelableSpan.writeToParcelInternal(android.os.Parcel,int)");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.view.WindowManager.requestAppKeyboardShortcuts(android.view.WindowManager$KeyboardShortcutsReceiver,int)");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract boolean javax.microedition.khronos.egl.EGL10.eglReleaseThread()");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract void org.w3c.dom.ls.LSSerializer.setFilter(org.w3c.dom.ls.LSSerializerFilter)");
+ HIDDEN_INTERFACE_WHITELIST.add("public abstract org.w3c.dom.ls.LSSerializerFilter org.w3c.dom.ls.LSSerializer.getFilter()");
+ }
+
public enum JDiffType {
INTERFACE, CLASS
}
@@ -1100,6 +1117,40 @@
}
/**
+ * Validate that an interfaces method count is as expected.
+ */
+ private List<String> checkInterfaceMethodCompliance() {
+ List<String> unexpectedMethods = new ArrayList<>();
+ for (Method method : mClass.getDeclaredMethods()) {
+ if (method.isDefault()) {
+ continue;
+ }
+ if (method.isSynthetic()) {
+ continue;
+ }
+ if (method.isBridge()) {
+ continue;
+ }
+ if (HIDDEN_INTERFACE_WHITELIST.contains(method.toGenericString())) {
+ continue;
+ }
+
+ boolean foundMatch = false;
+ for (JDiffMethod jdiffMethod : jDiffMethods) {
+ if (matches(jdiffMethod, method)) {
+ foundMatch = true;
+ }
+ }
+ if (!foundMatch) {
+ unexpectedMethods.add(method.toGenericString());
+ }
+ }
+
+ return unexpectedMethods;
+
+ }
+
+ /**
* Checks that the class found through reflection matches the
* specification from the API xml file.
*/
@@ -1123,6 +1174,15 @@
return;
}
+
+ List<String> methods = checkInterfaceMethodCompliance();
+ if (JDiffType.INTERFACE.equals(mClassType) && methods.size() > 0) {
+ mResultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE_METHOD,
+ mAbsoluteClassName, "Interfaces cannot be modified: "
+ + mAbsoluteClassName + ": " + methods);
+ return;
+ }
+
if (!checkClassModifiersCompliance()) {
logMismatchInterfaceSignature(mAbsoluteClassName,
"Non-compatible class found when looking for " +
diff --git a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
index 5bc0155..29e8e80 100644
--- a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
+++ b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -470,6 +471,8 @@
mActivityRule.runOnUiThread(() -> {
set.start();
+ verify(setListener, times(0)).onAnimationEnd(any(AnimatorSet.class),
+ any(boolean.class));
});
verify(setListener, within(100)).onAnimationEnd(set, false);
verify(listener1, times(1)).onAnimationEnd(a1, false);
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
index a365ac2..435e7c3 100644
--- a/tests/tests/content/Android.mk
+++ b/tests/tests/content/Android.mk
@@ -22,6 +22,8 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_USE_AAPT2 := true
+LOCAL_JNI_SHARED_LIBRARIES := libnativecursorwindow_jni libnativehelper_compat_libc++
+
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 \
@@ -57,3 +59,5 @@
LOCAL_COMPATIBILITY_SUITE := cts
include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 0234b8c..b22ab1e 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -265,6 +265,13 @@
</intent-filter>
</activity>
+ <provider
+ android:name="android.content.cts.CursorWindowContentProvider"
+ android:authorities="cursorwindow.provider"
+ android:exported="true"
+ android:process=":providerProcess">
+ </provider>
+
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/content/jni/Android.mk b/tests/tests/content/jni/Android.mk
new file mode 100644
index 0000000..4737b35
--- /dev/null
+++ b/tests/tests/content/jni/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libnativecursorwindow_jni
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := NativeCursorWindow.c
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+
+LOCAL_SHARED_LIBRARIES := libnativehelper_compat_libc++ liblog
+LOCAL_CXX_STL := libc++_static
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/content/jni/NativeCursorWindow.c b/tests/tests/content/jni/NativeCursorWindow.c
new file mode 100644
index 0000000..a2fb92a
--- /dev/null
+++ b/tests/tests/content/jni/NativeCursorWindow.c
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NativeCursorWindow"
+
+#include <jni.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <linux/ashmem.h>
+#include <utils/Log.h>
+
+struct Header {
+ // Offset of the lowest unused byte in the window.
+ uint32_t freeOffset;
+
+ // Offset of the first row slot chunk.
+ uint32_t firstChunkOffset;
+
+ uint32_t numRows;
+ uint32_t numColumns;
+};
+
+struct RowSlot {
+ uint32_t offset;
+};
+
+#define ROW_SLOT_CHUNK_NUM_ROWS 100
+
+struct RowSlotChunk {
+ struct RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS];
+ uint32_t nextChunkOffset;
+};
+
+/* Field types. */
+enum {
+ FIELD_TYPE_NULL = 0,
+ FIELD_TYPE_INTEGER = 1,
+ FIELD_TYPE_FLOAT = 2,
+ FIELD_TYPE_STRING = 3,
+ FIELD_TYPE_BLOB = 4,
+};
+
+/* Opaque type that describes a field slot. */
+struct FieldSlot {
+ int32_t type;
+ union {
+ double d;
+ int64_t l;
+ struct {
+ uint32_t offset;
+ uint32_t size;
+ } buffer;
+ } data;
+} __attribute((packed));
+
+JNIEXPORT jint JNICALL
+Java_android_content_cts_CursorWindowContentProvider_makeNativeCursorWindowFd(JNIEnv *env, jclass clazz,
+jint offset, jint size, jboolean isBlob) {
+ int fd = open("/dev/ashmem", O_RDWR);
+ ioctl(fd, ASHMEM_SET_NAME, "Fake CursorWindow");
+
+ ioctl(fd, ASHMEM_SET_SIZE, 1024);
+
+ void *data = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ struct Header *header = (struct Header *) data;
+ unsigned rowSlotChunkOffset = sizeof(struct Header);
+ struct RowSlotChunk *rowSlotChunk = (struct RowSlotChunk *)(data + rowSlotChunkOffset);
+ unsigned fieldSlotOffset = rowSlotChunkOffset + sizeof(struct RowSlotChunk);
+ struct FieldSlot *fieldSlot = (struct FieldSlot *) (data + fieldSlotOffset);
+
+ header->numRows = 1;
+ header->numColumns = 1;
+ header->firstChunkOffset = rowSlotChunkOffset;
+
+ rowSlotChunk->slots[0].offset = fieldSlotOffset;
+
+ fieldSlot->type = isBlob ? FIELD_TYPE_BLOB : FIELD_TYPE_STRING;
+ fieldSlot->data.buffer.offset = offset;
+ fieldSlot->data.buffer.size = size;
+
+ munmap(data, 1024);
+
+ return fd;
+
+}
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index 47813f3..b69a8c5 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -28,6 +28,7 @@
import android.provider.AlarmClock;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
import android.test.AndroidTestCase;
@@ -50,6 +51,23 @@
}
/**
+ * Assert target intent is not resolved by a filter with priority greater than 0.
+ * @param intent - the Intent will be handled.
+ */
+ private void assertDefaultHandlerValidPriority(final Intent intent) {
+ PackageManager packageManager = mContext.getPackageManager();
+ List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0);
+ assertNotNull(resolveInfoList);
+ // one or more activity can handle this intent.
+ assertTrue(resolveInfoList.size() > 0);
+ // no activities override defaults with a high priority. Only system activities can override
+ // the priority.
+ for (ResolveInfo resolveInfo : resolveInfoList) {
+ assertTrue(resolveInfo.priority <= 0);
+ }
+ }
+
+ /**
* Test ACTION_VIEW when url is http://web_address,
* it will open a browser window to the URL specified.
*/
@@ -308,4 +326,16 @@
public void testManageStorage() {
assertCanBeHandled(new Intent(StorageManager.ACTION_MANAGE_STORAGE));
}
+
+ public void testVoiceCommand() {
+ Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
+ assertCanBeHandled(intent);
+ assertDefaultHandlerValidPriority(intent);
+ }
+
+ public void testVoiceSearchHandsFree() {
+ Intent intent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ assertCanBeHandled(intent);
+ assertDefaultHandlerValidPriority(intent);
+ }
}
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java b/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java
new file mode 100644
index 0000000..004b193
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.content.cts;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Test {@link CursorWindowContentProvider} .
+ */
+public class ContentProviderCursorWindowTest extends AndroidTestCase {
+ private static final String TAG = "ContentProviderCursorWindowTest";
+
+ public void testQuery() throws IOException {
+ Cursor cursor = getContext().getContentResolver().query(
+ Uri.parse("content://cursorwindow.provider/hello"),
+ null, null, null, null
+ );
+ try {
+ cursor.moveToFirst();
+
+ int type = cursor.getType(0);
+ if (type != Cursor.FIELD_TYPE_BLOB) {
+ fail("Unexpected type " + type);
+ }
+ byte[] blob = cursor.getBlob(0);
+ Log.i(TAG, "Blob length " + blob.length);
+ fail("getBlob should fail due to invalid offset used in the field slot");
+ } catch (SQLiteException expected) {
+ Log.i(TAG, "Expected exception: " + expected);
+ } finally {
+ cursor.close();
+ }
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/CursorWindowContentProvider.java b/tests/tests/content/src/android/content/cts/CursorWindowContentProvider.java
new file mode 100644
index 0000000..4266f35
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/CursorWindowContentProvider.java
@@ -0,0 +1,130 @@
+/*
+ * 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.content.cts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.AbstractWindowedCursor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+/**
+ * Content provider that uses a custom {@link CursorWindow} to inject file descriptor
+ * pointing to another ashmem region having window slots with references outside of allowed ranges.
+ *
+ * <p>Used in {@link ContentProviderCursorWindowTest}
+ */
+public class CursorWindowContentProvider extends ContentProvider {
+ private static final String TAG = "CursorWindowContentProvider";
+ static {
+ System.loadLibrary("nativecursorwindow_jni");
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ AbstractWindowedCursor cursor = new AbstractWindowedCursor() {
+ @Override
+ public int getCount() {
+ return 1;
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return new String[] {"a"};
+ }
+ };
+ cursor.setWindow(new InjectingCursorWindow("TmpWindow"));
+ return cursor;
+ }
+
+ class InjectingCursorWindow extends CursorWindow {
+ InjectingCursorWindow(String name) {
+ super(name);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Parcel tmp = Parcel.obtain();
+
+ super.writeToParcel(tmp, flags);
+ tmp.setDataPosition(0);
+ // Find location of file descriptor
+ int fdPos = -1;
+ while (tmp.dataAvail() > 0) {
+ fdPos = tmp.dataPosition();
+ int frameworkFdMarker = tmp.readInt();
+ if (frameworkFdMarker == 0x66642a85 /* BINDER_TYPE_FD */) {
+ break;
+ }
+ }
+ if (fdPos == -1) {
+ tmp.recycle();
+ throw new IllegalStateException("File descriptor not found in the output of "
+ + "CursorWindow.writeToParcel");
+ }
+ // Write reply with replaced file descriptor
+ ParcelFileDescriptor evilFd = ParcelFileDescriptor
+ .adoptFd(makeNativeCursorWindowFd(1000, 1000, true));
+ dest.appendFrom(tmp, 0, fdPos);
+ dest.writeFileDescriptor(evilFd.getFileDescriptor());
+ tmp.setDataPosition(dest.dataPosition());
+ dest.appendFrom(tmp, dest.dataPosition(), tmp.dataAvail());
+ tmp.recycle();
+ }
+ }
+
+ private native static int makeNativeCursorWindowFd(int offset, int size, boolean isBlob);
+
+ // Stubs
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ Log.e(TAG, "delete() not implemented");
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ Log.e(TAG, "getType() not implemented");
+ return "";
+ }
+
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+ Log.e(TAG, "insert() not implemented");
+ return null;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ Log.e(TAG, "update() not implemented");
+ return 0;
+ }
+
+}
diff --git a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
index ec3ca74..ba2f1fd 100644
--- a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
+++ b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
@@ -16,7 +16,6 @@
package android.database.cts;
-
import android.content.Context;
import android.database.AbstractCursor;
import android.database.CharArrayBuffer;
@@ -27,9 +26,9 @@
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.Settings;
import android.test.InstrumentationTestCase;
-import java.lang.Math;
import java.io.File;
import java.util.ArrayList;
import java.util.Random;
@@ -135,9 +134,9 @@
}
public void testSetNotificationUri() {
- String MOCK_URI = "content://abstractrcursortest/testtable";
+ final Uri testUri = Settings.System.getUriFor(Settings.System.TIME_12_24);
mDatabaseCursor.setNotificationUri(getInstrumentation().getContext().getContentResolver(),
- Uri.parse(MOCK_URI));
+ testUri);
}
public void testRespond() {
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index 5992add..0e4c3d6 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -342,7 +342,7 @@
WindowManager.LayoutParams params = getWindow().getAttributes();
params.preferredDisplayModeId = mModeId;
- params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+ params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
params.setTitle("CtsTestPresentation");
getWindow().setAttributes(params);
}
diff --git a/tests/tests/graphics/assets/green-p3.png b/tests/tests/graphics/assets/green-p3.png
new file mode 100644
index 0000000..02f4cd1
--- /dev/null
+++ b/tests/tests/graphics/assets/green-p3.png
Binary files differ
diff --git a/tests/tests/graphics/assets/green-srgb.png b/tests/tests/graphics/assets/green-srgb.png
new file mode 100644
index 0000000..8b4d5ef
--- /dev/null
+++ b/tests/tests/graphics/assets/green-srgb.png
Binary files differ
diff --git a/tests/tests/graphics/assets/translucent-green-p3.png b/tests/tests/graphics/assets/translucent-green-p3.png
new file mode 100644
index 0000000..8066e69
--- /dev/null
+++ b/tests/tests/graphics/assets/translucent-green-p3.png
Binary files differ
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
new file mode 100644
index 0000000..05ab179
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.cts;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ColorSpace;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BitmapColorSpaceTest {
+ private Resources mResources;
+
+ @Before
+ public void setup() {
+ mResources = InstrumentationRegistry.getTargetContext().getResources();
+ }
+
+ @Test
+ public void sRGB() {
+ Bitmap b = BitmapFactory.decodeResource(mResources, R.drawable.robot);
+ ColorSpace cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
+ }
+
+ @Test
+ public void p3() {
+ try (InputStream in = mResources.getAssets().open("green-p3.png")) {
+ Bitmap b = BitmapFactory.decodeStream(in);
+ ColorSpace cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
+ } catch (IOException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void extendedSRGB() {
+ try (InputStream in = mResources.getAssets().open("prophoto-rgba16f.png")) {
+ Bitmap b = BitmapFactory.decodeStream(in);
+ ColorSpace cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
+ } catch (IOException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void reconfigure() {
+ try (InputStream in = mResources.getAssets().open("green-p3.png")) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inMutable = true;
+
+ Bitmap b = BitmapFactory.decodeStream(in, null, opts);
+ ColorSpace cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
+
+ b.reconfigure(b.getWidth() / 2, b.getHeight() / 2, Bitmap.Config.RGBA_F16);
+ cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
+
+ b.reconfigure(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888);
+ cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
+ } catch (IOException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void reuse() {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inMutable = true;
+
+ Bitmap bitmap1 = null;
+ try (InputStream in = mResources.getAssets().open("green-srgb.png")) {
+ bitmap1 = BitmapFactory.decodeStream(in, null, opts);
+ ColorSpace cs = bitmap1.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
+ } catch (IOException e) {
+ fail();
+ }
+
+ try (InputStream in = mResources.getAssets().open("green-p3.png")) {
+ opts.inBitmap = bitmap1;
+
+ Bitmap bitmap2 = BitmapFactory.decodeStream(in, null, opts);
+ assertSame(bitmap1, bitmap2);
+ ColorSpace cs = bitmap2.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
+ } catch (IOException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void getPixel() {
+ verifyGetPixel("green-p3.png", 0x75fb4cff, 0xff03ff00);
+ verifyGetPixel("translucent-green-p3.png", 0x3a7d267f, 0x7f00ff00); // 50% translucent
+ }
+
+ private void verifyGetPixel(@NonNull String fileName,
+ @ColorInt int rawColor, @ColorInt int srgbColor) {
+ try (InputStream in = mResources.getAssets().open(fileName)) {
+ Bitmap b = BitmapFactory.decodeStream(in);
+ ColorSpace cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
+
+ ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
+ b.copyPixelsToBuffer(dst);
+ dst.rewind();
+ // Stored as RGBA
+ assertEquals(rawColor, dst.asIntBuffer().get());
+
+ int srgb = b.getPixel(31, 31);
+ assertEquals(srgbColor, srgb);
+ } catch (IOException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void getPixels() {
+ verifyGetPixels("green-p3.png", 0xff03ff00);
+ verifyGetPixels("translucent-green-p3.png", 0x7f00ff00); // 50% translucent
+ }
+
+ private void verifyGetPixels(@NonNull String fileName, @ColorInt int expected) {
+ try (InputStream in = mResources.getAssets().open(fileName)) {
+ Bitmap b = BitmapFactory.decodeStream(in);
+ ColorSpace cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
+
+ int[] pixels = new int[b.getWidth() * b.getHeight()];
+ b.getPixels(pixels, 0, b.getWidth(), 0, 0, b.getWidth(), b.getHeight());
+ for (int pixel : pixels) {
+ assertEquals(expected, pixel);
+ }
+ } catch (IOException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void setPixel() {
+ verifySetPixel("green-p3.png", 0xffff0000, 0xea3424ff);
+ verifySetPixel("translucent-green-p3.png", 0x7fff0000, 0x751a127f);
+ }
+
+ private void verifySetPixel(@NonNull String fileName,
+ @ColorInt int newColor, @ColorInt int expectedColor) {
+ try (InputStream in = mResources.getAssets().open(fileName)) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inMutable = true;
+
+ Bitmap b = BitmapFactory.decodeStream(in, null, opts);
+ ColorSpace cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
+
+ b.setPixel(0, 0, newColor);
+
+ ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
+ b.copyPixelsToBuffer(dst);
+ dst.rewind();
+ // Stored as RGBA
+ assertEquals(expectedColor, dst.asIntBuffer().get());
+ } catch (IOException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void setPixels() {
+ verifySetPixels("green-p3.png", 0xffff0000, 0xea3424ff);
+ verifySetPixels("translucent-green-p3.png", 0x7fff0000, 0x751a127f);
+ }
+
+ private void verifySetPixels(@NonNull String fileName,
+ @ColorInt int newColor, @ColorInt int expectedColor) {
+ try (InputStream in = mResources.getAssets().open(fileName)) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inMutable = true;
+
+ Bitmap b = BitmapFactory.decodeStream(in, null, opts);
+ ColorSpace cs = b.getColorSpace();
+ assertNotNull(cs);
+ assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
+
+ int[] pixels = new int[b.getWidth() * b.getHeight()];
+ Arrays.fill(pixels, newColor);
+ b.setPixels(pixels, 0, b.getWidth(), 0, 0, b.getWidth(), b.getHeight());
+
+ ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
+ b.copyPixelsToBuffer(dst);
+ dst.rewind();
+
+ IntBuffer buffer = dst.asIntBuffer();
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < pixels.length; i++) {
+ // Stored as RGBA
+ assertEquals(expectedColor, buffer.get());
+ }
+ } catch (IOException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void writeColorSpace() {
+ verifyColorSpaceMarshalling("green-srgb.png", ColorSpace.get(ColorSpace.Named.SRGB));
+ verifyColorSpaceMarshalling("green-p3.png", ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+ verifyColorSpaceMarshalling("prophoto-rgba16f.png",
+ ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB));
+
+ // Special case where the color space will be null in native
+ Bitmap bitmapIn = BitmapFactory.decodeResource(mResources, R.drawable.robot);
+ verifyParcelUnparcel(bitmapIn, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ private void verifyColorSpaceMarshalling(
+ @NonNull String fileName, @NonNull ColorSpace colorSpace) {
+ try (InputStream in = mResources.getAssets().open(fileName)) {
+ Bitmap bitmapIn = BitmapFactory.decodeStream(in);
+ verifyParcelUnparcel(bitmapIn, colorSpace);
+ } catch (IOException e) {
+ fail();
+ }
+ }
+
+ private void verifyParcelUnparcel(Bitmap bitmapIn, ColorSpace expected) {
+ ColorSpace cs = bitmapIn.getColorSpace();
+ assertNotNull(cs);
+ assertSame(expected, cs);
+
+ Parcel p = Parcel.obtain();
+ bitmapIn.writeToParcel(p, 0);
+ p.setDataPosition(0);
+
+ Bitmap bitmapOut = Bitmap.CREATOR.createFromParcel(p);
+ cs = bitmapOut.getColorSpace();
+ assertNotNull(cs);
+ assertSame(expected, cs);
+
+ p.recycle();
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
index cd3d2e0..8cbde3a 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
@@ -29,6 +29,7 @@
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.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -710,6 +711,15 @@
}
@Test
+ public void testTransferParameters() {
+ ColorSpace.Rgb colorSpace = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
+ assertNotNull(colorSpace.getTransferParameters());
+
+ colorSpace = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
+ assertNull(colorSpace.getTransferParameters());
+ }
+
+ @Test
public void testIdempotentTransferFunctions() {
Arrays.stream(ColorSpace.Named.values())
.map(ColorSpace::get)
@@ -723,6 +733,26 @@
}
@Test
+ public void testMatch() {
+ for (ColorSpace.Named named : ColorSpace.Named.values()) {
+ ColorSpace cs = ColorSpace.get(named);
+ if (cs.getModel() == ColorSpace.Model.RGB) {
+ ColorSpace.Rgb rgb = (ColorSpace.Rgb) cs;
+ // match() cannot match extended sRGB
+ if (rgb != ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB) &&
+ rgb != ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB)) {
+ assertSame(cs,
+ ColorSpace.match(rgb.getTransform(), rgb.getTransferParameters()));
+ }
+ }
+ }
+
+ assertSame(ColorSpace.get(ColorSpace.Named.SRGB),
+ ColorSpace.match(SRGB_TO_XYZ, new ColorSpace.Rgb.TransferParameters(
+ 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4)));
+ }
+
+ @Test
public void testRendererSize() {
Bitmap b = ColorSpace.createRenderer()
.size(0)
@@ -751,6 +781,21 @@
assertNotNull(b);
}
+ @Test
+ public void testUcsRenderer() {
+ Bitmap b = ColorSpace.createRenderer()
+ .size(1024)
+ .clip(true)
+ .showWhitePoint(false)
+ .uniformChromaticityScale(true)
+ .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
+ .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffffff)
+ .add(ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB), 0.1f, 0.5f, 0.1f, 0xff000000)
+ .add(ColorSpace.get(ColorSpace.Named.ADOBE_RGB), 0.1f, 0.5f, 0.1f, 0xff000000)
+ .render();
+ assertNotNull(b);
+ }
+
@SuppressWarnings("SameParameterValue")
private static void assertArrayNotEquals(float[] a, float[] b, float eps) {
for (int i = 0; i < a.length; i++) {
diff --git a/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java b/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java
index 6cf7e29..1b00975 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/cts/FontRequestTest.java
@@ -15,6 +15,10 @@
*/
package android.graphics.fonts.cts;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -22,23 +26,31 @@
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.Base64;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Tests for {@link android.graphics.fonts.FontRequest}.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class FontRequestTest {
- private static final String PROVIDER = "com.test.fontprovider";
- private static final String QUERY = "my_family_name";
+ private static final String PROVIDER = "com.test.fontprovider.authority";
+ private static final String QUERY = "query";
+ private static final String PACKAGE = "com.test.fontprovider.package";
+ private static final byte[] BYTE_ARRAY =
+ Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
+ private static final List<List<byte[]>> CERTS = Arrays.asList(Arrays.asList(BYTE_ARRAY));
@Test
public void testWriteToParcel() {
- // GIVEN a FontRequest
- FontRequest request = new FontRequest(PROVIDER, QUERY);
+ // GIVEN a FontRequest created with the long constructor
+ FontRequest request = new FontRequest(PROVIDER, PACKAGE, QUERY, CERTS);
// WHEN we write it to a Parcel
Parcel dest = Parcel.obtain();
@@ -48,30 +60,76 @@
// THEN we create from that parcel and get the same values.
FontRequest result = FontRequest.CREATOR.createFromParcel(dest);
assertEquals(PROVIDER, result.getProviderAuthority());
+ assertEquals(PACKAGE, result.getProviderPackage());
assertEquals(QUERY, result.getQuery());
+ assertEquals(CERTS.size(), result.getCertificates().size());
+ List<byte[]> cert = CERTS.get(0);
+ List<byte[]> resultCert = result.getCertificates().get(0);
+ assertEquals(cert.size(), resultCert.size());
+ assertTrue(Arrays.equals(cert.get(0), resultCert.get(0)));
}
@Test
- public void testConstructorWithNullAuthority() {
- try {
- // WHEN we create a request with a null authority
- new FontRequest(null, QUERY);
- } catch (NullPointerException e) {
- // THEN we expect an exception to be raised.
- return;
- }
- fail();
+ public void testWriteToParcel_shortConstructor() {
+ // GIVEN a FontRequest created with the short constructor
+ FontRequest request = new FontRequest(PROVIDER, PACKAGE, QUERY);
+
+ // WHEN we write it to a Parcel
+ Parcel dest = Parcel.obtain();
+ request.writeToParcel(dest, 0);
+ dest.setDataPosition(0);
+
+ // THEN we create from that parcel and get the same values.
+ FontRequest result = FontRequest.CREATOR.createFromParcel(dest);
+ assertEquals(PROVIDER, result.getProviderAuthority());
+ assertEquals(PACKAGE, result.getProviderPackage());
+ assertEquals(QUERY, result.getQuery());
+ assertNotNull(result.getCertificates());
+ assertEquals(0, result.getCertificates().size());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testShortConstructor_nullAuthority() {
+ new FontRequest(null, PACKAGE, QUERY);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testShortConstructor_nullPackage() {
+ new FontRequest(PROVIDER, null, QUERY);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testShortConstructor_nullQuery() {
+ new FontRequest(PROVIDER, PACKAGE, null);
}
@Test
- public void testConstructorWithNullQuery() {
- try {
- // WHEN we create a request with a null query
- new FontRequest(PROVIDER, null);
- } catch (NullPointerException e) {
- // THEN we expect an exception to be raised.
- return;
- }
- fail();
+ public void testShortConstructor_defaults() {
+ // WHEN we create a request with the short constructor
+ FontRequest request = new FontRequest(PROVIDER, PACKAGE, QUERY);
+
+ // THEN we expect the defaults to be the following values
+ assertNotNull(request.getCertificates());
+ assertEquals(0, request.getCertificates().size());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLongConstructor_nullAuthority() {
+ new FontRequest(null, PACKAGE, QUERY, CERTS);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLongConstructor_nullPackage() {
+ new FontRequest(PROVIDER, null, QUERY, CERTS);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLongConstructor_nullQuery() {
+ new FontRequest(PROVIDER, PACKAGE, null, CERTS);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLongConstructor_nullCerts() {
+ new FontRequest(PROVIDER, PACKAGE, QUERY, null);
}
}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java b/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java
index 06e2e3f..4f961de 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/cts/FontResultTest.java
@@ -90,16 +90,9 @@
assertEquals(STYLE, result.getStyle());
}
- @Test
+ @Test(expected = NullPointerException.class)
public void testConstructorWithNullFileDescriptor() {
- try {
- // WHEN we create a result with a null file descriptor
- new FontResult(null, TTC_INDEX, FONT_VARIATION_SETTINGS, STYLE);
- } catch (NullPointerException e) {
- // THEN we expect an exception to be raised.
- return;
- }
- fail();
+ new FontResult(null, TTC_INDEX, FONT_VARIATION_SETTINGS, STYLE);
}
@Test
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
index 5993a95..1642e5c 100644
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
@@ -846,8 +846,6 @@
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
.setDigests(digest)
- .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
String padding = getSignatureAlgorithmPadding(signatureAlgorithm);
diff --git a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
index 58f5313..4704d3d 100644
--- a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
@@ -17,6 +17,7 @@
package android.media.cts;
import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.os.Parcel;
import com.android.compatibility.common.util.CtsAndroidTestCase;
@@ -65,4 +66,27 @@
assertTrue("Source and target attributes are not considered equal",
srcAttr.equals(targetAttr));
}
+
+ // Test case 3: verify going from AudioAttributes to stream type with null arg
+ public void testGetVolumeControlStreamNull() throws Exception {
+ try {
+ int streamType = AudioAttributes.getVolumeControlStream(null);
+ fail("null AudioAttributes are not allowed");
+ } catch (IllegalArgumentException e) {
+ // test success
+ }
+ }
+
+ // Test case 4: verify going from AudioAttributes to stream type, with attributes built from
+ // stream type.
+ public void testGetVolumeControlStreamVsLegacyStream() throws Exception {
+ for (int testType : new int[] { AudioManager.STREAM_ALARM, AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_NOTIFICATION, AudioManager.STREAM_RING,
+ AudioManager.STREAM_SYSTEM, AudioManager.STREAM_VOICE_CALL}) {
+ final AudioAttributes aa = new AudioAttributes.Builder().setLegacyStreamType(testType)
+ .build();
+ final int stream = AudioAttributes.getVolumeControlStream(aa);
+ assertEquals("Volume control from attributes, stream doesn't match", testType, stream);
+ }
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index a732bc5..2651af5 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -21,6 +21,7 @@
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
+import android.os.Parcel;
import android.util.Log;
import android.media.AudioPlaybackConfiguration;
@@ -62,6 +63,51 @@
private final static int TEST_USAGE = AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
private final static int TEST_CONTENT = AudioAttributes.CONTENT_TYPE_SPEECH;
+ // test marshalling/unmarshalling of an AudioPlaybackConfiguration instance. Since we can't
+ // create an AudioPlaybackConfiguration directly, we first need to play something to get one.
+ public void testParcelableWriteToParcel() throws Exception {
+ if (!isValidPlatform("testParcelableWriteToParcel")) return;
+
+ // create a player, make it play so we can get an AudioPlaybackConfiguration instance
+ AudioManager am = new AudioManager(getContext());
+ assertNotNull("Could not create AudioManager", am);
+ final AudioAttributes aa = (new AudioAttributes.Builder())
+ .setUsage(TEST_USAGE)
+ .setContentType(TEST_CONTENT)
+ .build();
+ mMp = MediaPlayer.create(getContext(), R.raw.sine1khzs40dblong,
+ aa, am.generateAudioSessionId());
+ mMp.start();
+ Thread.sleep(2*TEST_TIMING_TOLERANCE_MS);// waiting for playback to start
+ List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
+ mMp.stop();
+ assertTrue("No playback reported", configs.size() > 0);
+ AudioPlaybackConfiguration configToMarshall = null;
+ for (AudioPlaybackConfiguration config : configs) {
+ if (config.getAudioAttributes().equals(aa)) {
+ configToMarshall = config;
+ break;
+ }
+ }
+
+ assertNotNull("Configuration not found during playback", configToMarshall);
+ assertEquals(0, configToMarshall.describeContents());
+
+ final Parcel srcParcel = Parcel.obtain();
+ final Parcel dstParcel = Parcel.obtain();
+ final byte[] mbytes;
+
+ configToMarshall.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
+ mbytes = srcParcel.marshall();
+ dstParcel.unmarshall(mbytes, 0, mbytes.length);
+ dstParcel.setDataPosition(0);
+ final AudioPlaybackConfiguration restoredConfig =
+ AudioPlaybackConfiguration.CREATOR.createFromParcel(dstParcel);
+
+ assertEquals("Marshalled/restored AudioAttributes don't match",
+ configToMarshall.getAudioAttributes(), restoredConfig.getAudioAttributes());
+ }
+
public void testGetterMediaPlayer() throws Exception {
if (!isValidPlatform("testGetterMediaPlayer")) return;
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
index 9dbcd5e..813742a 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
@@ -1350,7 +1350,20 @@
for (Size sz : standardSizes) {
MediaFormat format = MediaFormat.createVideoFormat(
MIME_TYPE, sz.getWidth(), sz.getHeight());
- format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); // require at least 15fps
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ int bitRate = BITRATE_DEFAULT;
+ if (sz.getWidth() == 1920 && sz.getHeight() == 1080) {
+ bitRate = BITRATE_1080p;
+ } else if (sz.getWidth() == 1280 && sz.getHeight() == 720) {
+ bitRate = BITRATE_720p;
+ } else if (sz.getWidth() == 800 && sz.getHeight() == 480) {
+ bitRate = BITRATE_800x480;
+ }
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ Log.i(TAG,"format = " + format.toString());
if (mcl.findEncoderForFormat(format) != null) {
return sz;
}
diff --git a/tests/tests/ndef/src/android/ndef/cts/NdefTest.java b/tests/tests/ndef/src/android/ndef/cts/NdefTest.java
index 47bdfa2..969806a 100644
--- a/tests/tests/ndef/src/android/ndef/cts/NdefTest.java
+++ b/tests/tests/ndef/src/android/ndef/cts/NdefTest.java
@@ -190,13 +190,13 @@
// 3 records, 7 chunks
assertEquals(new NdefMessage(
- new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, null, null, new byte[] {1,2,3,4}),
+ new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, new byte[] {0x21}, null, new byte[] {1,2,3,4}),
new NdefRecord(NdefRecord.TNF_EMPTY, null, null, null),
- new NdefRecord(NdefRecord.TNF_MIME_MEDIA, null, null, new byte[] {11,12,13,14})),
+ new NdefRecord(NdefRecord.TNF_MIME_MEDIA, new byte[] {0x21}, null, new byte[] {11,12,13,14})),
new NdefMessage(new byte[] {
- (byte)0xB4, 0, 1, 1, (byte)0x36, 0, 2, 2, 3, (byte)0x16, 0, 1, 4,
+ (byte)0xB4, 1, 1, (byte)0x21, 1, (byte)0x36, 0, 2, 2, 3, (byte)0x16, 0, 1, 4,
(byte)0x10, 0, 0,
- (byte)0x32, 0, 2, 11, 12, (byte)0x36, 0, 1, 13, (byte)0x56, 0, 1, 14
+ (byte)0x32, 1, 2, (byte)0x21, 11, 12, (byte)0x36, 0, 1, 13, (byte)0x56, 0, 1, 14
}));
// 255 byte payload
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index 185ebfa..24871ca 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -18,6 +18,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -384,6 +385,57 @@
}
/**
+ * Exercises the requestNetwork with NetworkCallback API. This checks to
+ * see if we get a callback for an INTERNET request.
+ */
+ public void testRequestNetworkCallback() {
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build(), callback);
+
+ try {
+ // Wait to get callback for availability of internet
+ Network internetNetwork = callback.waitForAvailable();
+ assertNotNull("Did not receive NetworkCallback#onAvailable for INTERNET",
+ internetNetwork);
+ } catch (InterruptedException e) {
+ fail("NetworkCallback wait was interrupted.");
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ }
+ }
+
+ /**
+ * Exercises the requestNetwork with NetworkCallback API with timeout - expected to
+ * fail. Use WIFI and switch Wi-Fi off.
+ */
+ public void testRequestNetworkCallback_onUnavailable() {
+ final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
+ if (previousWifiEnabledState) {
+ disconnectFromWifi(null);
+ }
+
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build(), 100, callback);
+
+ try {
+ // Wait to get callback for unavailability of requested network
+ assertTrue("Did not receive NetworkCallback#onUnavailable",
+ callback.waitForUnavailable());
+ } catch (InterruptedException e) {
+ fail("NetworkCallback wait was interrupted.");
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ if (previousWifiEnabledState) {
+ connectToWifi();
+ }
+ }
+ }
+
+ /**
* Tests reporting of connectivity changed.
*/
public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
@@ -639,6 +691,7 @@
private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
private final CountDownLatch mLostLatch = new CountDownLatch(1);
+ private final CountDownLatch mUnavailableLatch = new CountDownLatch(1);
public Network currentNetwork;
public Network lastLostNetwork;
@@ -651,6 +704,11 @@
return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
}
+ public boolean waitForUnavailable() throws InterruptedException {
+ return mUnavailableLatch.await(2, TimeUnit.SECONDS);
+ }
+
+
@Override
public void onAvailable(Network network) {
currentNetwork = network;
@@ -665,6 +723,11 @@
}
mLostLatch.countDown();
}
+
+ @Override
+ public void onUnavailable() {
+ mUnavailableLatch.countDown();
+ }
}
private Network getWifiNetwork() {
diff --git a/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
new file mode 100644
index 0000000..fcd0454
--- /dev/null
+++ b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -0,0 +1,675 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiManager;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.Characteristics;
+import android.net.wifi.aware.DiscoverySessionCallback;
+import android.net.wifi.aware.IdentityChangedListener;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.PublishDiscoverySession;
+import android.net.wifi.aware.SubscribeConfig;
+import android.net.wifi.aware.SubscribeDiscoverySession;
+import android.net.wifi.aware.WifiAwareManager;
+import android.net.wifi.aware.WifiAwareSession;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wi-Fi Aware CTS test suite: single device testing. Performs tests on a single
+ * device to validate Wi-Fi Aware.
+ */
+public class SingleDeviceTest extends AndroidTestCase {
+ private static final String TAG = "WifiAwareCtsTests";
+
+ // wait for Wi-Fi Aware to become available
+ static private final int WAIT_FOR_AWARE_CHANGE_SECS = 10;
+
+ private final Object mLock = new Object();
+ private final HandlerThread mHandlerThread = new HandlerThread("SingleDeviceTest");
+ private final Handler mHandler;
+ {
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ private WifiAwareManager mWifiAwareManager;
+ private WifiManager mWifiManager;
+ private WifiManager.WifiLock mWifiLock;
+ private ConnectivityManager mConnectivityManager;
+
+ // used to store any WifiAwareSession allocated during tests - will clean-up after tests
+ private List<WifiAwareSession> mSessions = new ArrayList<>();
+
+ private class WifiAwareBroadcastReceiver extends BroadcastReceiver {
+ private CountDownLatch mBlocker = new CountDownLatch(1);
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED.equals(intent.getAction())) {
+ mBlocker.countDown();
+ }
+ }
+
+ boolean waitForStateChange() throws InterruptedException {
+ return mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+ }
+ }
+
+ private class AttachCallbackTest extends AttachCallback {
+ static final int ATTACHED = 0;
+ static final int ATTACH_FAILED = 1;
+ static final int ERROR = 2; // no callback: timeout, interruption
+
+ private CountDownLatch mBlocker = new CountDownLatch(1);
+ private int mCallbackCalled = ERROR; // garbage init
+ private WifiAwareSession mSession = null;
+
+ @Override
+ public void onAttached(WifiAwareSession session) {
+ mCallbackCalled = ATTACHED;
+ mSession = session;
+ synchronized (mLock) {
+ mSessions.add(session);
+ }
+ mBlocker.countDown();
+ }
+
+ @Override
+ public void onAttachFailed() {
+ mCallbackCalled = ATTACH_FAILED;
+ mBlocker.countDown();
+ }
+
+ /**
+ * Waits for any of the callbacks to be called - or an error (timeout, interruption).
+ * Returns one of the ATTACHED, ATTACH_FAILED, or ERROR values.
+ */
+ int waitForAnyCallback() {
+ try {
+ boolean noTimeout = mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+ if (noTimeout) {
+ return mCallbackCalled;
+ } else {
+ return ERROR;
+ }
+ } catch (InterruptedException e) {
+ return ERROR;
+ }
+ }
+
+ /**
+ * Access the session created by a callback. Only useful to be called after calling
+ * waitForAnyCallback() and getting the ATTACHED code back.
+ */
+ WifiAwareSession getSession() {
+ return mSession;
+ }
+ }
+
+ private class IdentityChangedListenerTest extends IdentityChangedListener {
+ private CountDownLatch mBlocker = new CountDownLatch(1);
+ private byte[] mMac = null;
+
+ @Override
+ public void onIdentityChanged(byte[] mac) {
+ mMac = mac;
+ mBlocker.countDown();
+ }
+
+ /**
+ * Waits for the listener callback to be called - or an error (timeout, interruption).
+ * Returns true on callback called, false on error (timeout, interruption).
+ */
+ boolean waitForListener() {
+ try {
+ return mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the MAC address of the discovery interface supplied to the triggered callback.
+ */
+ byte[] getMac() {
+ return mMac;
+ }
+ }
+
+ private class DiscoverySessionCallbackTest extends DiscoverySessionCallback {
+ static final int ON_PUBLISH_STARTED = 0;
+ static final int ON_SUBSCRIBE_STARTED = 1;
+ static final int ON_SESSION_CONFIG_UPDATED = 2;
+ static final int ON_SESSION_CONFIG_FAILED = 3;
+ static final int ON_SESSION_TERMINATED = 4;
+ static final int ON_SERVICE_DISCOVERED = 5;
+ static final int ON_MESSAGE_SEND_SUCCEEDED = 6;
+ static final int ON_MESSAGE_SEND_FAILED = 7;
+ static final int ON_MESSAGE_RECEIVED = 8;
+
+ private final Object mLocalLock = new Object();
+
+ private CountDownLatch mBlocker;
+ private int mCurrentWaitForCallback;
+ private ArrayDeque<Integer> mCallbackQueue = new ArrayDeque<>();
+
+ private PublishDiscoverySession mPublishDiscoverySession;
+ private SubscribeDiscoverySession mSubscribeDiscoverySession;
+
+ private void processCallback(int callback) {
+ synchronized (mLocalLock) {
+ if (mBlocker != null && mCurrentWaitForCallback == callback) {
+ mBlocker.countDown();
+ } else {
+ mCallbackQueue.addLast(callback);
+ }
+ }
+ }
+
+ @Override
+ public void onPublishStarted(PublishDiscoverySession session) {
+ mPublishDiscoverySession = session;
+ processCallback(ON_PUBLISH_STARTED);
+ }
+
+ @Override
+ public void onSubscribeStarted(SubscribeDiscoverySession session) {
+ mSubscribeDiscoverySession = session;
+ processCallback(ON_SUBSCRIBE_STARTED);
+ }
+
+ @Override
+ public void onSessionConfigUpdated() {
+ processCallback(ON_SESSION_CONFIG_UPDATED);
+ }
+
+ @Override
+ public void onSessionConfigFailed() {
+ processCallback(ON_SESSION_CONFIG_FAILED);
+ }
+
+ @Override
+ public void onSessionTerminated() {
+ processCallback(ON_SESSION_TERMINATED);
+ }
+
+ @Override
+ public void onServiceDiscovered(PeerHandle peerHandle, byte[] serviceSpecificInfo,
+ List<byte[]> matchFilter) {
+ processCallback(ON_SERVICE_DISCOVERED);
+ }
+
+ @Override
+ public void onMessageSendSucceeded(int messageId) {
+ processCallback(ON_MESSAGE_SEND_SUCCEEDED);
+ }
+
+ @Override
+ public void onMessageSendFailed(int messageId) {
+ processCallback(ON_MESSAGE_SEND_FAILED);
+ }
+
+ @Override
+ public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
+ processCallback(ON_MESSAGE_RECEIVED);
+ }
+
+ /**
+ * Wait for the specified callback - any of the ON_* constants. Returns a true
+ * on success (specified callback triggered) or false on failure (timed-out or
+ * interrupted while waiting for the requested callback).
+ *
+ * Note: other callbacks happening while while waiting for the specified callback will
+ * be queued.
+ */
+ boolean waitForCallback(int callback) {
+ synchronized (mLocalLock) {
+ boolean found = mCallbackQueue.remove(callback);
+ if (found) {
+ return true;
+ }
+
+ mCurrentWaitForCallback = callback;
+ mBlocker = new CountDownLatch(1);
+ }
+
+ try {
+ return mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Indicates whether the specified callback (any of the ON_* constants) has already
+ * happened and in the queue. Useful when the order of events is important.
+ */
+ boolean hasCallbackAlreadyHappened(int callback) {
+ synchronized (mLocalLock) {
+ return mCallbackQueue.contains(callback);
+ }
+ }
+
+ /**
+ * Returns the last created publish discovery session.
+ */
+ PublishDiscoverySession getPublishDiscoverySession() {
+ PublishDiscoverySession session = mPublishDiscoverySession;
+ mPublishDiscoverySession = null;
+ return session;
+ }
+
+ /**
+ * Returns the last created subscribe discovery session.
+ */
+ SubscribeDiscoverySession getSubscribeDiscoverySession() {
+ SubscribeDiscoverySession session = mSubscribeDiscoverySession;
+ mSubscribeDiscoverySession = null;
+ return session;
+ }
+ }
+
+ private class NetworkCallbackTest extends ConnectivityManager.NetworkCallback {
+ private CountDownLatch mBlocker = new CountDownLatch(1);
+
+ @Override
+ public void onUnavailable() {
+ mBlocker.countDown();
+ }
+
+ /**
+ * Wait for the onUnavailable() callback to be triggered. Returns true if triggered,
+ * otherwise (timed-out, interrupted) returns false.
+ */
+ boolean waitForOnUnavailable() {
+ try {
+ return mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ mWifiAwareManager = (WifiAwareManager) getContext().getSystemService(
+ Context.WIFI_AWARE_SERVICE);
+ assertNotNull("Wi-Fi Aware Manager", mWifiAwareManager);
+
+ mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+ assertNotNull("Wi-Fi Manager", mWifiManager);
+ mWifiLock = mWifiManager.createWifiLock(TAG);
+ mWifiLock.acquire();
+ if (!mWifiManager.isWifiEnabled()) {
+ mWifiManager.setWifiEnabled(true);
+ }
+
+ mConnectivityManager = (ConnectivityManager) getContext().getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ assertNotNull("Connectivity Manager", mConnectivityManager);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
+ WifiAwareBroadcastReceiver receiver = new WifiAwareBroadcastReceiver();
+ mContext.registerReceiver(receiver, intentFilter);
+ if (!mWifiAwareManager.isAvailable()) {
+ assertTrue("Timeout waiting for Wi-Fi Aware to change status",
+ receiver.waitForStateChange());
+ assertTrue("Wi-Fi Aware is not available (should be)", mWifiAwareManager.isAvailable());
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ super.tearDown();
+ return;
+ }
+
+ synchronized (mLock) {
+ for (WifiAwareSession session : mSessions) {
+ // no damage from destroying twice (i.e. ok if test cleaned up after itself already)
+ session.destroy();
+ }
+ mSessions.clear();
+ }
+
+ super.tearDown();
+ }
+
+ /**
+ * Validate:
+ * - Characteristics are available
+ * - Characteristics values are legitimate. Not in the CDD. However, the tested values are
+ * based on the Wi-Fi Aware protocol.
+ */
+ public void testCharacteristics() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ Characteristics characteristics = mWifiAwareManager.getCharacteristics();
+ assertNotNull("Wi-Fi Aware characteristics are null", characteristics);
+ assertEquals("Service Name Length", characteristics.getMaxServiceNameLength(), 255);
+ assertEquals("Service Specific Information Length",
+ characteristics.getMaxServiceSpecificInfoLength(), 255);
+ assertEquals("Match Filter Length", characteristics.getMaxMatchFilterLength(), 255);
+ }
+
+ /**
+ * Validate that on Wi-Fi Aware availability change we get a broadcast + the API returns
+ * correct status.
+ */
+ public void testAvailabilityStatusChange() throws Exception {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
+
+ // 1. Disable Wi-Fi
+ WifiAwareBroadcastReceiver receiver1 = new WifiAwareBroadcastReceiver();
+ mContext.registerReceiver(receiver1, intentFilter);
+ mWifiManager.setWifiEnabled(false);
+
+ assertTrue("Timeout waiting for Wi-Fi Aware to change status",
+ receiver1.waitForStateChange());
+ assertFalse("Wi-Fi Aware is available (should not be)", mWifiAwareManager.isAvailable());
+
+ // 2. Enable Wi-Fi
+ WifiAwareBroadcastReceiver receiver2 = new WifiAwareBroadcastReceiver();
+ mContext.registerReceiver(receiver2, intentFilter);
+ mWifiManager.setWifiEnabled(true);
+
+ assertTrue("Timeout waiting for Wi-Fi Aware to change status",
+ receiver2.waitForStateChange());
+ assertTrue("Wi-Fi Aware is not available (should be)", mWifiAwareManager.isAvailable());
+ }
+
+ /**
+ * Validate that can attach to Wi-Fi Aware.
+ */
+ public void testAttachNoIdentity() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ WifiAwareSession session = attachAndGetSession();
+ session.destroy();
+ }
+
+ /**
+ * Validate that can attach to Wi-Fi Aware and get identity information. Use the identity
+ * information to validate that MAC address changes on every attach.
+ *
+ * Note: relies on no other entity using Wi-Fi Aware during the CTS test. Since if it is used
+ * then the attach/destroy will not correspond to enable/disable and will not result in a new
+ * MAC address being generated.
+ */
+ public void testAttachDiscoveryAddressChanges() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ final int numIterations = 10;
+ Set<TestUtils.MacWrapper> macs = new HashSet<>();
+
+ for (int i = 0; i < numIterations; ++i) {
+ AttachCallbackTest attachCb = new AttachCallbackTest();
+ IdentityChangedListenerTest identityL = new IdentityChangedListenerTest();
+ mWifiAwareManager.attach(attachCb, identityL, mHandler);
+ assertEquals("Wi-Fi Aware attach: iteration " + i, AttachCallbackTest.ATTACHED,
+ attachCb.waitForAnyCallback());
+ assertTrue("Wi-Fi Aware attach: iteration " + i, identityL.waitForListener());
+
+ WifiAwareSession session = attachCb.getSession();
+ assertNotNull("Wi-Fi Aware session: iteration " + i, session);
+
+ byte[] mac = identityL.getMac();
+ assertNotNull("Wi-Fi Aware discovery MAC: iteration " + i, mac);
+
+ session.destroy();
+
+ macs.add(new TestUtils.MacWrapper(mac));
+ }
+
+ assertEquals("", numIterations, macs.size());
+ }
+
+ /**
+ * Validate a successful publish discovery session lifetime: publish, update publish, destroy.
+ */
+ public void testPublishDiscoverySuccess() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ final String serviceName = "ValidName";
+
+ WifiAwareSession session = attachAndGetSession();
+
+ PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
+ serviceName).build();
+ DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
+
+ // 1. publish
+ session.publish(publishConfig, discoveryCb, mHandler);
+ assertTrue("Publish started",
+ discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
+ PublishDiscoverySession discoverySession = discoveryCb.getPublishDiscoverySession();
+ assertNotNull("Publish session", discoverySession);
+
+ // 2. update-publish
+ publishConfig = new PublishConfig.Builder().setServiceName(
+ serviceName).setServiceSpecificInfo("extras".getBytes()).build();
+ discoverySession.updatePublish(publishConfig);
+ assertTrue("Publish update", discoveryCb.waitForCallback(
+ DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
+
+ // 3. destroy
+ assertFalse("Publish not terminated", discoveryCb.hasCallbackAlreadyHappened(
+ DiscoverySessionCallbackTest.ON_SESSION_TERMINATED));
+ discoverySession.destroy();
+
+ // 4. try update post-destroy: should time-out waiting for cb
+ discoverySession.updatePublish(publishConfig);
+ assertFalse("Publish update post destroy", discoveryCb.waitForCallback(
+ DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
+
+ session.destroy();
+ }
+
+ /**
+ * Validate a successful subscribe discovery session lifetime: subscribe, update subscribe,
+ * destroy.
+ */
+ public void testSubscribeDiscoverySuccess() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ final String serviceName = "ValidName";
+
+ WifiAwareSession session = attachAndGetSession();
+
+ SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(
+ serviceName).build();
+ DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
+
+ // 1. subscribe
+ session.subscribe(subscribeConfig, discoveryCb, mHandler);
+ assertTrue("Subscribe started",
+ discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_SUBSCRIBE_STARTED));
+ SubscribeDiscoverySession discoverySession = discoveryCb.getSubscribeDiscoverySession();
+ assertNotNull("Subscribe session", discoverySession);
+
+ // 2. update-subscribe
+ subscribeConfig = new SubscribeConfig.Builder().setServiceName(
+ serviceName).setServiceSpecificInfo("extras".getBytes()).build();
+ discoverySession.updateSubscribe(subscribeConfig);
+ assertTrue("Subscribe update", discoveryCb.waitForCallback(
+ DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
+
+ // 3. destroy
+ assertFalse("Subscribe not terminated", discoveryCb.hasCallbackAlreadyHappened(
+ DiscoverySessionCallbackTest.ON_SESSION_TERMINATED));
+ discoverySession.destroy();
+
+ // 4. try update post-destroy: should time-out waiting for cb
+ discoverySession.updateSubscribe(subscribeConfig);
+ assertFalse("Subscribe update post destroy", discoveryCb.waitForCallback(
+ DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
+
+ session.destroy();
+ }
+
+ /**
+ * Test the send message flow. Since testing single device cannot send to a real peer -
+ * validate that sending to a bogus peer fails.
+ */
+ public void testSendMessageFail() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ WifiAwareSession session = attachAndGetSession();
+
+ PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
+ "ValidName").build();
+ DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
+
+ // 1. publish
+ session.publish(publishConfig, discoveryCb, mHandler);
+ assertTrue("Publish started",
+ discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
+ PublishDiscoverySession discoverySession = discoveryCb.getPublishDiscoverySession();
+ assertNotNull("Publish session", discoverySession);
+
+ // 2. send a message with a null peer-handle - expect exception
+ try {
+ discoverySession.sendMessage(null, -1290, "some message".getBytes());
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // empty
+ }
+
+ discoverySession.destroy();
+ session.destroy();
+ }
+
+ /**
+ * Request an Aware data-path on a Publish discovery session (which can be done with a null
+ * peer - to accept all requests). Validate that times-out.
+ */
+ public void testDataPathInContextOfDiscoveryFail() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ WifiAwareSession session = attachAndGetSession();
+
+ PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
+ "ValidName").build();
+ DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
+ NetworkCallbackTest networkCb = new NetworkCallbackTest();
+
+ // 1. publish
+ session.publish(publishConfig, discoveryCb, mHandler);
+ assertTrue("Publish started",
+ discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
+ PublishDiscoverySession discoverySession = discoveryCb.getPublishDiscoverySession();
+ assertNotNull("Publish session", discoverySession);
+
+ // 2. request an AWARE network
+ NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+ NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
+ discoverySession.createNetworkSpecifier(null, null)).build();
+ mConnectivityManager.requestNetwork(nr, networkCb, 2000);
+ assertTrue("OnUnavailable received", networkCb.waitForOnUnavailable());
+
+ discoverySession.destroy();
+ session.destroy();
+ }
+
+ /**
+ * Request an Aware data-path as a Responder with no peer MAC address (i.e. accept any peer
+ * request). Validate that times-out.
+ */
+ public void testDataPathOutOfBandFail() {
+ if (!TestUtils.shouldTestWifiAware(getContext())) {
+ return;
+ }
+
+ WifiAwareSession session = attachAndGetSession();
+
+ PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
+ "ValidName").build();
+ DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
+ NetworkCallbackTest networkCb = new NetworkCallbackTest();
+
+ // 1. request an AWARE network
+ NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+ NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
+ session.createNetworkSpecifier(
+ WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, null, null)).build();
+ mConnectivityManager.requestNetwork(nr, networkCb, 2000);
+ assertTrue("OnUnavailable received", networkCb.waitForOnUnavailable());
+
+ session.destroy();
+ }
+
+ // local utilities
+
+ private WifiAwareSession attachAndGetSession() {
+ AttachCallbackTest attachCb = new AttachCallbackTest();
+ mWifiAwareManager.attach(attachCb, mHandler);
+ int cbCalled = attachCb.waitForAnyCallback();
+ assertEquals("Wi-Fi Aware attach", AttachCallbackTest.ATTACHED, cbCalled);
+
+ WifiAwareSession session = attachCb.getSession();
+ assertNotNull("Wi-Fi Aware session", session);
+
+ return session;
+ }
+}
diff --git a/tests/tests/net/src/android/net/wifi/aware/cts/TestUtils.java b/tests/tests/net/src/android/net/wifi/aware/cts/TestUtils.java
new file mode 100644
index 0000000..a12c8bb
--- /dev/null
+++ b/tests/tests/net/src/android/net/wifi/aware/cts/TestUtils.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import java.util.Arrays;
+
+/**
+ * Test utilities for Wi-Fi Aware CTS test suite.
+ */
+class TestUtils {
+ static final String TAG = "WifiAwareCtsTests";
+
+ /**
+ * Returns a flag indicating whether or not Wi-Fi Aware should be tested. Wi-Fi Aware
+ * should be tested if the feature is supported on the current device.
+ */
+ static boolean shouldTestWifiAware(Context context) {
+ final PackageManager pm = context.getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
+ }
+
+ /**
+ * Wraps a byte[] (MAC address representation). Intended to provide hash and equality operators
+ * so that the MAC address can be used in containers.
+ */
+ static class MacWrapper {
+ private byte[] mMac;
+
+ MacWrapper(byte[] mac) {
+ mMac = mac;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof MacWrapper)) {
+ return false;
+ }
+
+ MacWrapper lhs = (MacWrapper) o;
+ return Arrays.equals(mMac, lhs.mMac);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mMac);
+ }
+ }
+}
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index c520145..5bfcda3 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -49,6 +49,7 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" />
<protected-broadcast android:name="android.intent.action.UID_REMOVED" />
<protected-broadcast android:name="android.intent.action.QUERY_PACKAGE_RESTART" />
<protected-broadcast android:name="android.intent.action.CONFIGURATION_CHANGED" />
@@ -81,6 +82,10 @@
<protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
<protected-broadcast android:name="android.intent.action.USER_INITIALIZE" />
<protected-broadcast android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION" />
+ <protected-broadcast android:name="android.intent.action.OVERLAY_ADDED" />
+ <protected-broadcast android:name="android.intent.action.OVERLAY_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.OVERLAY_REMOVED" />
+ <protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
@@ -100,6 +105,7 @@
<protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
<protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
<protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
+ <protected-broadcast android:name="android.app.action.SHOW_DEVICE_MONITORING_DIALOG" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_DELETED" />
@@ -138,6 +144,7 @@
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
+ <protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" />
<protected-broadcast android:name="android.bluetooth.devicepicker.action.LAUNCH" />
<protected-broadcast android:name="android.bluetooth.devicepicker.action.DEVICE_SELECTED" />
<protected-broadcast
@@ -147,6 +154,8 @@
<protected-broadcast
android:name="android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT" />
<protected-broadcast
+ android:name="android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED" />
+ <protected-broadcast
android:name="android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED" />
@@ -163,6 +172,8 @@
<protected-broadcast
android:name="android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED" />
<protected-broadcast
+ android:name="android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED" />
+ <protected-broadcast
android:name="android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED" />
@@ -177,8 +188,22 @@
<protected-broadcast
android:name="android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS" />
<protected-broadcast
+ android:name="android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY" />
+ <protected-broadcast
+ android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" />
+ <protected-broadcast
+ android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
+ <protected-broadcast
android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.btopp.intent.action.INCOMING_FILE_NOTIFICATION" />
<protected-broadcast android:name="android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT" />
<protected-broadcast android:name="android.btopp.intent.action.LIST" />
@@ -189,10 +214,15 @@
<protected-broadcast android:name="android.btopp.intent.action.RETRY" />
<protected-broadcast android:name="android.btopp.intent.action.OPEN" />
<protected-broadcast android:name="android.btopp.intent.action.OPEN_INBOUND" />
+ <protected-broadcast android:name="android.btopp.intent.action.TRANSFER_COMPLETE" />
+ <protected-broadcast android:name="android.btopp.intent.action.ACCEPT" />
+ <protected-broadcast android:name="com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN" />
<protected-broadcast android:name="com.android.bluetooth.pbap.authchall" />
<protected-broadcast android:name="com.android.bluetooth.pbap.userconfirmtimeout" />
<protected-broadcast android:name="com.android.bluetooth.pbap.authresponse" />
<protected-broadcast android:name="com.android.bluetooth.pbap.authcancelled" />
+ <protected-broadcast android:name="com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT" />
+ <protected-broadcast android:name="com.android.bluetooth.sap.action.DISCONNECT_ACTION" />
<protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" />
@@ -276,6 +306,7 @@
<protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" />
+ <protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
<protected-broadcast android:name="android.net.wifi.RSSI_CHANGED" />
<protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
@@ -306,7 +337,7 @@
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
- <protected-broadcast android:name="com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE" />
+ <protected-broadcast android:name="com.android.server.ACTION_TRIGGER_IDLE" />
<protected-broadcast android:name="android.intent.action.HDMI_PLUGGED" />
@@ -388,6 +419,8 @@
<protected-broadcast android:name="android.bluetooth.adapter.action.BLE_STATE_CHANGED" />
<protected-broadcast android:name="com.android.bluetooth.map.USER_CONFIRM_TIMEOUT" />
+ <protected-broadcast android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" />
+ <protected-broadcast android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
<protected-broadcast android:name="android.content.jobscheduler.JOB_DELAY_EXPIRED" />
<protected-broadcast android:name="android.content.syncmanager.SYNC_ALARM" />
<protected-broadcast android:name="android.media.INTERNAL_RINGER_MODE_CHANGED_ACTION" />
@@ -399,6 +432,9 @@
<protected-broadcast android:name="android.os.action.DISCHARGING" />
<protected-broadcast android:name="android.search.action.SEARCHABLES_CHANGED" />
<protected-broadcast android:name="android.security.STORAGE_CHANGED" />
+ <protected-broadcast android:name="android.security.action.TRUST_STORE_CHANGED" />
+ <protected-broadcast android:name="android.security.action.KEYCHAIN_CHANGED" />
+ <protected-broadcast android:name="android.security.action.KEY_ACCESS_CHANGED" />
<protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" />
<protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" />
<protected-broadcast android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
@@ -411,8 +447,10 @@
<protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
<protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
+ <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
<protected-broadcast android:name="EventConditionProvider.EVALUATE" />
+ <protected-broadcast android:name="SnoozeHelper.EVALUATE" />
<protected-broadcast android:name="wifi_scan_available" />
<protected-broadcast android:name="action.cne.started" />
@@ -430,6 +468,7 @@
<protected-broadcast android:name="com.android.server.Wifi.action.TOGGLE_PNO" />
<protected-broadcast android:name="intent.action.ACTION_RF_BAND_INFO" />
<protected-broadcast android:name="android.intent.action.MEDIA_RESOURCE_GRANTED" />
+ <protected-broadcast android:name="android.app.action.NETWORK_LOGS_AVAILABLE" />
<protected-broadcast android:name="android.app.action.SECURITY_LOGS_AVAILABLE" />
<protected-broadcast android:name="android.app.action.INTERRUPTION_FILTER_CHANGED" />
@@ -465,12 +504,6 @@
<protected-broadcast android:name="android.net.wifi.PASSPOINT_ICON_RECEIVED" />
<protected-broadcast android:name="com.android.server.notification.CountdownConditionProvider" />
- <protected-broadcast android:name="com.android.ims.IMS_SERVICE_UP" />
- <protected-broadcast android:name="com.android.ims.IMS_INCOMING_CALL" />
- <protected-broadcast android:name="com.android.ims.internal.uce.UCE_SERVICE_UP" />
- <protected-broadcast android:name="com.android.intent.action.IMS_FEATURE_CHANGED" />
- <protected-broadcast android:name="com.android.intent.action.IMS_CONFIG_CHANGED" />
-
<protected-broadcast android:name="com.android.internal.location.ALARM_WAKEUP" />
<protected-broadcast android:name="com.android.internal.location.ALARM_TIMEOUT" />
<protected-broadcast android:name="android.intent.action.GLOBAL_BUTTON" />
@@ -479,7 +512,16 @@
<protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
<protected-broadcast android:name="com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK" />
+ <protected-broadcast android:name="com.android.server.retaildemo.ACTION_RESET_DEMO" />
+
+ <protected-broadcast android:name="android.intent.action.DEVICE_LOCKED_CHANGED" />
+
<!-- Added in O -->
+ <!-- TODO: temporary broadcast used by AutoFillManagerServiceImpl; will be removed -->
+ <protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
+ <protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
+ <protected-broadcast android:name="com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION" />
+
<protected-broadcast android:name="android.content.pm.action.SESSION_COMMITTED" />
<!-- ====================================================================== -->
@@ -698,6 +740,7 @@
android:priority="400" />
<!-- Allows an app to access precise location.
+ Alternatively, you might want {@link #ACCESS_COARSE_LOCATION}.
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCESS_FINE_LOCATION"
@@ -707,6 +750,7 @@
android:protectionLevel="dangerous|ephemeral" />
<!-- Allows an app to access approximate location.
+ Alternatively, you might want {@link #ACCESS_FINE_LOCATION}.
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCESS_COARSE_LOCATION"
@@ -766,6 +810,16 @@
android:description="@string/permdesc_callPhone"
android:protectionLevel="dangerous" />
+ <!-- Allows an application to manage its own calls, but rely on the system to route focus to the
+ currently active call.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.MANAGE_OWN_CALLS"
+ android:permissionGroup="android.permission-group.PHONE"
+ android:label="@string/permlab_manageOwnCalls"
+ android:description="@string/permdesc_manageOwnCalls"
+ android:protectionLevel="dangerous" />
+
<!-- Allows an application to access the IMS call service: making and
modifying a call
<p>Protection level: signature|privileged
@@ -843,6 +897,17 @@
android:description="@string/permdesc_processOutgoingCalls"
android:protectionLevel="dangerous" />
+
+ <!-- Allows the app to answer an incoming phone call.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.ANSWER_PHONE_CALLS"
+ android:permissionGroup="android.permission-group.PHONE"
+ android:label="@string/permlab_answerPhoneCalls"
+ android:description="@string/permdesc_answerPhoneCalls"
+ android:protectionLevel="dangerous" />
+
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the device microphone -->
<!-- ====================================================================== -->
@@ -1041,7 +1106,8 @@
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows an application to send SMS to premium shortcodes without user permission.
- <p>Not for use by third-party applications. -->
+ <p>Not for use by third-party applications.
+ @hide -->
<permission android:name="android.permission.SEND_SMS_NO_CONFIRMATION"
android:protectionLevel="signature|privileged" />
@@ -1052,7 +1118,8 @@
<!-- @SystemApi Allows an application to receive emergency cell broadcast messages,
to record or display them to the user.
- <p>Not for use by third-party applications. -->
+ <p>Not for use by third-party applications.
+ @hide -->
<permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST"
android:protectionLevel="signature|privileged" />
@@ -1218,6 +1285,12 @@
<permission android:name="android.permission.SCORE_NETWORKS"
android:protectionLevel="signature|privileged" />
+ <!-- Allows applications to request network
+ recommendations and scores from the NetworkScoreService.
+ <p>Not for use by third-party applications. @hide -->
+ <permission android:name="android.permission.REQUEST_NETWORK_SCORES"
+ android:protectionLevel="signature|setup" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
@@ -1316,6 +1389,7 @@
android:protectionLevel="dangerous"
android:description="@string/permdesc_getAccounts"
android:label="@string/permlab_getAccounts" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!-- @SystemApi Allows applications to call into AccountAuthenticators.
<p>Not for use by third-party applications. -->
@@ -1432,7 +1506,7 @@
<!-- @SystemApi Allows reading the OEM unlock state
@hide <p>Not for use by third-party applications. -->
<permission android:name="android.permission.READ_OEM_UNLOCK_STATE"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged" />
<!-- @hide Allows enabling/disabling OEM unlock
<p>Not for use by third-party applications. -->
@@ -1512,6 +1586,14 @@
<permission android:name="android.permission.BIND_INCALL_SERVICE"
android:protectionLevel="signature|privileged" />
+ <!-- Must be required by a link {@link android.telephony.VisualVoicemailService} to ensure that
+ only the system can bind to it.
+ <p>Protection level: signature|privileged
+ -->
+ <permission
+ android:name="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
+ android:protectionLevel="signature|privileged"/>
+
<!-- Must be required by a {@link android.telecom.CallScreeningService},
to ensure that only the system can bind to it.
<p>Protection level: signature|privileged
@@ -1545,6 +1627,16 @@
<permission android:name="android.permission.RECEIVE_STK_COMMANDS"
android:protectionLevel="signature|privileged" />
+ <!-- Must be required by an ImsService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature|privileged
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_IMS_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
@@ -1572,6 +1664,12 @@
<permission android:name="android.permission.CACHE_CONTENT"
android:protectionLevel="signature" />
+ <!-- Allows an application to aggressively allocate disk space.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.ALLOCATE_AGGRESSIVE"
+ android:protectionLevel="signature|privileged" />
+
<!-- ================================== -->
<!-- Permissions for screenlock -->
<!-- ================================== -->
@@ -1636,11 +1734,11 @@
<!-- @hide Allows an application to create, remove users and get the list of
users on the device. Applications holding this permission can only create restricted,
- guest, managed, and ephemeral users. For creating other kind of users,
+ guest, managed, demo, and ephemeral users. For creating other kind of users,
{@link android.Manifest.permission#MANAGE_USERS} is needed.
This permission is not available to third party applications. -->
<permission android:name="android.permission.CREATE_USERS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature" />
<!-- @hide Allows an application to set the profile owners and the device owner.
This permission is not available to third party applications.-->
@@ -1710,7 +1808,7 @@
<eat-comment />
<!-- Allows an app to create windows using the type
- {@link android.view.WindowManager.LayoutParams#TYPE_SYSTEM_ALERT},
+ {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
shown on top of all other apps. Very few apps
should use this permission; these windows are intended for
system-level interaction with the user.
@@ -1729,6 +1827,22 @@
android:description="@string/permdesc_systemAlertWindow"
android:protectionLevel="signature|preinstalled|appop|pre23|development" />
+ <!-- Allows an app to run in the background.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.RUN_IN_BACKGROUND"
+ android:label="@string/permlab_runInBackground"
+ android:description="@string/permdesc_runInBackground"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an app to use data in the background.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.USE_DATA_IN_BACKGROUND"
+ android:label="@string/permlab_useDataInBackground"
+ android:description="@string/permdesc_useDataInBackground"
+ android:protectionLevel="signature" />
+
<!-- ================================== -->
<!-- Permissions affecting the system wallpaper -->
<!-- ================================== -->
@@ -1760,6 +1874,14 @@
<permission android:name="android.permission.SET_TIME"
android:protectionLevel="signature|privileged" />
+ <!-- Allows applications to set the system time zone.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.SET_TIME_ZONE"
+ android:label="@string/permlab_setTimeZone"
+ android:description="@string/permdesc_setTimeZone"
+ android:protectionLevel="signature|privileged" />
+
<!-- ==================================================== -->
<!-- Permissions related to changing status bar -->
<!-- ==================================================== -->
@@ -1786,8 +1908,7 @@
android:description="@string/permdesc_install_shortcut"
android:protectionLevel="normal"/>
- <!-- Allows an application to uninstall a shortcut in Launcher.
- <p>Protection level: normal
+ <!--This permission is no longer supported.
-->
<permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
android:label="@string/permlab_uninstall_shortcut"
@@ -2015,6 +2136,23 @@
<permission android:name="android.permission.UPDATE_CONFIG"
android:protectionLevel="signature|privileged" />
+ <!-- Allows the system to reset throttling in shortcut manager.
+ @hide -->
+ <permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING"
+ android:protectionLevel="signature" />
+
+ <!-- Allows the system to bind to the discovered Network Recommendation Service.
+ @SystemApi @hide -->
+ <permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"/>
+
+ <!-- Allows an application to enable, disable and change priority of
+ runtime resource overlays.
+ @hide -->
+ <permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"
+ android:protectionLevel="signature|privileged" />
+
<!-- ========================================= -->
<!-- Permissions for special development tools -->
<!-- ========================================= -->
@@ -2263,6 +2401,11 @@
<permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by the CompanionDeviceManager to ensure that only the system can bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Must be required by the RuntimePermissionPresenterService to ensure
that only the system can bind to it.
@hide -->
@@ -2297,6 +2440,13 @@
<permission android:name="android.permission.BIND_VOICE_INTERACTION"
android:protectionLevel="signature" />
+ <!-- Must be required by a {@link android.service.autofill.AutoFillService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_AUTO_FILL"
+ android:protectionLevel="signature" />
+
<!-- Must be required by hotword enrollment application,
to ensure that only the system can interact with it.
@hide <p>Not for use by third-party applications.</p> -->
@@ -2393,13 +2543,23 @@
android:protectionLevel="signature" />
<!-- Allows an application to request installing packages. Apps
- targeting APIs greater than 22 must hold this permission in
+ targeting APIs greater than 25 must hold this permission in
order to use {@link android.content.Intent#ACTION_INSTALL_PACKAGE}.
- <p>Protection level: normal
+ <p>Protection level: signature
-->
<permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
android:label="@string/permlab_requestInstallPackages"
android:description="@string/permdesc_requestInstallPackages"
+ android:protectionLevel="signature|appop" />
+
+ <!-- Allows an application to request deleting packages. Apps
+ targeting APIs greater than 25 must hold this permission in
+ order to use {@link android.content.Intent#ACTION_UNINSTALL_PACKAGE}.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REQUEST_DELETE_PACKAGES"
+ android:label="@string/permlab_requestDeletePackages"
+ android:description="@string/permdesc_requestDeletePackages"
android:protectionLevel="normal" />
<!-- @SystemApi Allows an application to install packages.
@@ -2548,6 +2708,21 @@
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to set the volume key long-press listener.
+ <p>When it's set, the application will receive the volume key long-press event
+ instead of changing volume.</p>
+ <p>Not for use by third-party applications</p> -->
+ <permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- @SystemApi @hide Allows an application to set media key event listener.
+ <p>When it's set, the application will receive the media key event before
+ any other media sessions. If the event is handled by the listener, other sessions
+ cannot get the event.</p>
+ <p>Not for use by third-party applications</p> -->
+ <permission android:name="android.permission.SET_MEDIA_KEY_LISTENER"
+ android:protectionLevel="signature|privileged|development" />
+
<!-- @SystemApi Required to be able to disable the device (very dangerous!).
<p>Not for use by third-party applications.
@hide
@@ -2603,7 +2778,10 @@
android:protectionLevel="signature" />
<!-- @SystemApi Allows an application to broadcast privileged networking requests.
- <p>Not for use by third-party applications. @hide -->
+ <p>Not for use by third-party applications.
+ @hide
+ @deprecated Use {@link android.Manifest.permission#REQUEST_NETWORK_SCORES} instead
+ -->
<permission android:name="android.permission.BROADCAST_NETWORK_PRIVILEGED"
android:protectionLevel="signature|privileged" />
@@ -2662,6 +2840,8 @@
This is a normal permission: an app requesting it will always be granted the
permission, without the user needing to approve or see it. -->
<permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"
+ android:label="@string/permlab_requestIgnoreBatteryOptimizations"
+ android:description="@string/permdesc_requestIgnoreBatteryOptimizations"
android:protectionLevel="normal" />
<!-- @SystemApi Allows an application to collect battery statistics -->
@@ -2846,7 +3026,7 @@
any metadata and intents attached.
@hide -->
<permission android:name="android.permission.ACCESS_NOTIFICATIONS"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|appop" />
<!-- Marker permission for applications that wish to access notification policy.
<p>Protection level: normal
@@ -2910,12 +3090,13 @@
<permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:protectionLevel="signature" />
- <!-- Must be required by an {@link
- android.service.notification.NotificationRankerService to ensure that only the system can bind to it.
+ <!-- @SystemApi Must be required by an {@link
+ android.service.notification.NotificationAssistantService} to ensure that only the system
+ can bind to it.
<p>Protection level: signature
- @hide This is not a third-party API (intended for system apps). -->
+ @hide
-->
- <permission android:name="android.permission.BIND_NOTIFICATION_RANKER_SERVICE"
+ <permission android:name="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"
android:protectionLevel="signature" />
<!-- Must be required by a {@link
@@ -2941,6 +3122,13 @@
<permission android:name="android.permission.BIND_DREAM_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by an {@link android.app.usage.CacheQuotaService} to ensure that only the
+ system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_CACHE_QUOTA_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to call into a carrier setup flow. It is up to the
carrier setup application to enforce that this permission is required
@hide This is not a third-party API (intended for OEMs and system apps). -->
@@ -3035,10 +3223,10 @@
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
- <!-- Allows the holder to access the ephemeral applications on the device.
+ <!-- Allows the holder to access the instant applications on the device.
@hide -->
- <permission android:name="android.permission.ACCESS_EPHEMERAL_APPS"
- android:protectionLevel="signature" />
+ <permission android:name="android.permission.ACCESS_INSTANT_APPS"
+ android:protectionLevel="signature|installer" />
<!-- Allows receiving the usage of media resource e.g. video/audio codec and
graphic memory.
@@ -3076,6 +3264,11 @@
<permission android:name="android.permission.BIND_VR_LISTENER_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by system apps when accessing restricted VR APIs.
+ <p>Protection level: signature -->
+ <permission android:name="android.permission.RESTRICTED_VR_ACCESS"
+ android:protectionLevel="signature|preinstalled" />
+
<!-- Required to make calls to {@link android.service.vr.IVrManager}.
@hide -->
<permission android:name="android.permission.ACCESS_VR_MANAGER"
@@ -3092,6 +3285,21 @@
<permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to manage auto-fill sessions.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_AUTO_FILL"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an app to set the theme overlay in /vendor/overlay
+ being used.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MODIFY_THEME_OVERLAY"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an instant app to create foreground services. -->
+ <permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
+ android:protectionLevel="signature|development|ephemeral|appop" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
@@ -3101,7 +3309,7 @@
android:killAfterRestore="false"
android:icon="@drawable/ic_launcher_android"
android:supportsRtl="true"
- android:theme="@style/Theme.Material.Light.DarkActionBar"
+ android:theme="@style/Theme.DeviceDefault.Light.DarkActionBar"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true">
<activity android:name="com.android.internal.app.ChooserActivity"
@@ -3110,6 +3318,7 @@
android:excludeFromRecents="true"
android:documentLaunchMode="never"
android:relinquishTaskIdentity="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:process=":ui">
<intent-filter>
<action android:name="android.intent.action.CHOOSER" />
@@ -3137,7 +3346,7 @@
android:label="@string/managed_profile_label">
</activity-alias>
<activity android:name="com.android.internal.app.HeavyWeightSwitcherActivity"
- android:theme="@style/Theme.Material.Light.Dialog"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog"
android:label="@string/heavy_weight_switcher_title"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
@@ -3170,7 +3379,7 @@
<activity android:name="android.accounts.ChooseAccountActivity"
android:excludeFromRecents="true"
android:exported="true"
- android:theme="@style/Theme.Material.Light.Dialog"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog"
android:label="@string/choose_account_label"
android:process=":ui">
</activity>
@@ -3178,14 +3387,14 @@
<activity android:name="android.accounts.ChooseTypeAndAccountActivity"
android:excludeFromRecents="true"
android:exported="true"
- android:theme="@style/Theme.Material.Light.Dialog"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog"
android:label="@string/choose_account_label"
android:process=":ui">
</activity>
<activity android:name="android.accounts.ChooseAccountTypeActivity"
android:excludeFromRecents="true"
- android:theme="@style/Theme.Material.Light.Dialog"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog"
android:label="@string/choose_account_label"
android:process=":ui">
</activity>
@@ -3193,19 +3402,19 @@
<activity android:name="android.accounts.CantAddAccountActivity"
android:excludeFromRecents="true"
android:exported="true"
- android:theme="@style/Theme.Material.Light.Dialog.NoActionBar"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog.NoActionBar"
android:process=":ui">
</activity>
<activity android:name="android.accounts.GrantCredentialsPermissionActivity"
android:excludeFromRecents="true"
android:exported="true"
- android:theme="@style/Theme.Material.Light.DialogWhenLarge"
+ android:theme="@style/Theme.DeviceDefault.Light.DialogWhenLarge"
android:process=":ui">
</activity>
<activity android:name="android.content.SyncActivityTooManyDeletes"
- android:theme="@style/Theme.Material.Light.Dialog"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog"
android:label="@string/sync_too_many_deletes"
android:process=":ui">
</activity>
@@ -3225,7 +3434,7 @@
</activity>
<activity android:name="com.android.internal.app.NetInitiatedActivity"
- android:theme="@style/Theme.Material.Light.Dialog.Alert"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
android:excludeFromRecents="true"
android:process=":ui">
</activity>
@@ -3246,7 +3455,7 @@
<activity android:name="com.android.internal.app.ConfirmUserCreationActivity"
android:excludeFromRecents="true"
android:process=":ui"
- android:theme="@style/Theme.Material.Light.Dialog.Alert">
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert">
<intent-filter android:priority="1000">
<action android:name="android.os.action.CREATE_USER" />
<category android:name="android.intent.category.DEFAULT" />
@@ -3254,7 +3463,7 @@
</activity>
<activity android:name="com.android.internal.app.UnlaunchableAppActivity"
- android:theme="@style/Theme.Material.Light.Dialog.Alert"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
android:excludeFromRecents="true"
android:process=":ui">
</activity>
@@ -3314,11 +3523,22 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_CT_LOGS" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
<receiver android:name="com.android.server.MasterClearReceiver"
android:permission="android.permission.MASTER_CLEAR">
<intent-filter
android:priority="100" >
- <!-- For Checkin, Settings, etc.: action=MASTER_CLEAR -->
+ <!-- For Checkin, Settings, etc.: action=FACTORY_RESET -->
+ <action android:name="android.intent.action.FACTORY_RESET" />
+ <!-- As above until all the references to the deprecated MASTER_CLEAR get updated to
+ FACTORY_RESET. -->
<action android:name="android.intent.action.MASTER_CLEAR" />
<!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR -->
@@ -3357,10 +3577,17 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- <service
- android:name="com.android.server.BackgroundDexOptJobService"
- android:exported="true"
- android:permission="android.permission.BIND_JOB_SERVICE">
+ <service android:name="com.android.server.BackgroundDexOptJobService"
+ android:exported="true"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.PruneInstantAppsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.storage.DiskStatsLoggingService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
</application>
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index d47f06b..58611a3 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -119,6 +119,13 @@
&& declaredGroup.name.startsWith(PLATFORM_ROOT_NAMESPACE));
}
}
+
+ // OEMs cannot define new ephemeral permissions
+ for (String permission : declaredPermissionsMap.keySet()) {
+ PermissionInfo info = declaredPermissionsMap.get(permission);
+ assertFalse("Cannot define new ephemeral permission " + permission,
+ (info.protectionLevel & PermissionInfo.PROTECTION_FLAG_EPHEMERAL) != 0);
+ }
}
private List<PermissionInfo> loadExpectedPermissions() throws Exception {
diff --git a/tests/tests/provider/Android.mk b/tests/tests/provider/Android.mk
index ce0a890..2298378 100644
--- a/tests/tests/provider/Android.mk
+++ b/tests/tests/provider/Android.mk
@@ -31,6 +31,7 @@
LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v4 \
compatibility-device-util \
ctstestrunner \
ub-uiautomator \
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index 8c1c3cc..9a79c64 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -20,7 +20,8 @@
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
- <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
@@ -39,7 +40,6 @@
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
-
<application>
<uses-library android:name="android.test.runner"/>
@@ -82,6 +82,14 @@
<meta-data android:name="android.content.ContactDirectory" android:value="true" />
</provider>
+ <provider android:name="android.support.v4.content.FileProvider"
+ android:authorities="android.provider.cts.fileprovider"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
+
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/provider/res/xml/file_paths.xml b/tests/tests/provider/res/xml/file_paths.xml
new file mode 100644
index 0000000..486e87c
--- /dev/null
+++ b/tests/tests/provider/res/xml/file_paths.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <files-path name="debug" path="debug/" />
+</paths>
diff --git a/tests/tests/provider/src/android/provider/cts/GetResultActivity.java b/tests/tests/provider/src/android/provider/cts/GetResultActivity.java
index 382ca6f..566a7d6 100644
--- a/tests/tests/provider/src/android/provider/cts/GetResultActivity.java
+++ b/tests/tests/provider/src/android/provider/cts/GetResultActivity.java
@@ -69,4 +69,12 @@
}
return result;
}
+
+ public Result getResult(long timeout, TimeUnit unit) {
+ try {
+ return mResult.poll(timeout, unit);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreUiTest.java b/tests/tests/provider/src/android/provider/cts/MediaStoreUiTest.java
index 3ede99c..73b5559 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreUiTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreUiTest.java
@@ -22,25 +22,32 @@
import android.content.Intent;
import android.content.UriPermission;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.media.ExifInterface;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
-import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.cts.GetResultActivity.Result;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until;
+import android.support.v4.content.FileProvider;
import android.test.InstrumentationTestCase;
import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.KeyEvent;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.OutputStream;
@@ -48,6 +55,7 @@
import java.util.concurrent.TimeUnit;
public class MediaStoreUiTest extends InstrumentationTestCase {
+ private static final String TAG = "MediaStoreUiTest";
private static final int REQUEST_CODE = 42;
private static final String CONTENT = "Test";
@@ -122,6 +130,80 @@
}
}
+ /**
+ * Verify that whoever handles {@link MediaStore#ACTION_IMAGE_CAPTURE} can
+ * correctly write the contents into a passed {@code content://} Uri.
+ */
+ public void testImageCapture() throws Exception {
+ final Context context = getInstrumentation().getContext();
+ if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
+ Log.d(TAG, "Skipping due to lack of camera");
+ return;
+ }
+
+ final File targetDir = new File(context.getFilesDir(), "debug");
+ final File target = new File(targetDir, "capture.jpg");
+
+ targetDir.mkdirs();
+ assertFalse(target.exists());
+
+ final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT,
+ FileProvider.getUriForFile(context, "android.provider.cts.fileprovider", target));
+
+ // Figure out who is going to answer the phone
+ final ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0);
+ Log.d(TAG, "We're probably launching " + ri);
+
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ mDevice.waitForIdle();
+
+ // Try a couple different strategies for taking a photo: first take a
+ // photo and confirm using hardware keys
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_CAMERA);
+ mDevice.waitForIdle();
+ SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
+ mDevice.waitForIdle();
+
+ // Maybe that gave us a result?
+ Result result = mActivity.getResult(15, TimeUnit.SECONDS);
+ Log.d(TAG, "First pass result was " + result);
+
+ // Hrm, that didn't work; let's try an alternative approach of digging
+ // around for a shutter button
+ if (result == null) {
+ final String pkg = ri.activityInfo.packageName;
+ mDevice.findObject(new UiSelector().resourceId(pkg + ":id/shutter_button")).click();
+ mDevice.waitForIdle();
+ SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
+ mDevice.findObject(new UiSelector().resourceId(pkg + ":id/shutter_button")).click();
+ mDevice.waitForIdle();
+ }
+
+ result = mActivity.getResult(15, TimeUnit.SECONDS);
+ Log.d(TAG, "Second pass result was " + result);
+
+ assertNotNull("Expected to get a IMAGE_CAPTURE result; your camera app should "
+ + "respond to the CAMERA and DPAD_CENTER keycodes", result);
+
+ assertTrue("exists", target.exists());
+
+ // At the very least we expect photos generated by the device to have
+ // sane baseline EXIF data
+ final ExifInterface exif = new ExifInterface(new FileInputStream(target));
+ assertAttribute(exif, ExifInterface.TAG_MAKE);
+ assertAttribute(exif, ExifInterface.TAG_MODEL);
+ assertAttribute(exif, ExifInterface.TAG_DATETIME);
+ }
+
+ private static void assertAttribute(ExifInterface exif, String tag) {
+ final String res = exif.getAttribute(tag);
+ if (res == null || res.length() == 0) {
+ fail("Expected valid EXIF tag for tag " + tag);
+ }
+ }
+
private boolean supportsHardware() {
final PackageManager pm = getInstrumentation().getContext().getPackageManager();
return !pm.hasSystemFeature("android.hardware.type.television")
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
index 4257067..329031a 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
@@ -180,6 +180,7 @@
Log.i(LOG_TAG, "Service has been unbound");
sServiceUnBoundLatch.countDown();
sIsBound = false;
+ sConnectionService = null;
return super.onUnbind(intent);
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
index ee2f842..4185318 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
@@ -94,12 +94,14 @@
}
@Override
- public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
+ public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerHandle,
+ ConnectionRequest request) {
mLocks[CREATE_INCOMING_CONNECTION_FAILED_LOCK].countDown();
}
@Override
- public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+ public void onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerHandle,
+ ConnectionRequest request) {
mLocks[CREATE_OUTGOING_CONNECTION_FAILED_LOCK].countDown();
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
index 084c420..5ace9ff 100644
--- a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
@@ -27,6 +27,7 @@
import java.util.function.Predicate;
import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
+import static android.telecom.cts.TestUtils.waitOnAllHandlers;
/**
* CTS tests for the self-managed {@link android.telecom.ConnectionService} APIs.
@@ -37,7 +38,6 @@
public class SelfManagedConnectionServiceTest extends BaseTelecomTestWithMockServices {
private Uri TEST_ADDRESS_1 = Uri.fromParts("sip", "call1@test.com", null);
private Uri TEST_ADDRESS_2 = Uri.fromParts("sip", "call2@test.com", null);
- private Uri TEST_ADDRESS_3 = Uri.fromParts("sip", "call3@test.com", null);
@Override
protected void setUp() throws Exception {
@@ -197,7 +197,6 @@
// Expect there to be no managed calls at the moment.
assertFalse(mTelecomManager.isInManagedCall());
- assertMockInCallServiceUnbound();
setDisconnectedAndVerify(connection);
}
@@ -226,7 +225,6 @@
// Expect there to be no managed calls at the moment.
assertFalse(mTelecomManager.isInManagedCall());
- assertMockInCallServiceUnbound();
setDisconnectedAndVerify(connection);
}
@@ -346,6 +344,8 @@
connections.forEach((selfManagedConnection) ->
selfManagedConnection.disconnectAndDestroy());
+
+ waitOnAllHandlers(getInstrumentation());
}
public void testEmergencyCallOngoing() throws Exception {
diff --git a/tests/tests/telecom/src/android/telecom/cts/VideoCallTest.java b/tests/tests/telecom/src/android/telecom/cts/VideoCallTest.java
index 9f79628..7f2b27b 100644
--- a/tests/tests/telecom/src/android/telecom/cts/VideoCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/VideoCallTest.java
@@ -444,6 +444,16 @@
Connection.VideoProvider.SESSION_EVENT_RX_RESUME);
}
});
+
+ assertCallSessionEventReceived(inCallService.getVideoCallCallback(call),
+ VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR,
+ new Work() {
+ @Override
+ public void doWork() {
+ connection.sendMockCallSessionEvent(
+ Connection.VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR);
+ }
+ });
}
/**
diff --git a/tests/tests/telephony/AndroidManifest.xml b/tests/tests/telephony/AndroidManifest.xml
index 9601833..38d20c6 100644
--- a/tests/tests/telephony/AndroidManifest.xml
+++ b/tests/tests/telephony/AndroidManifest.xml
@@ -106,6 +106,16 @@
</service>
+ <service
+ android:name=".PermissionlessVisualVoicemailService"
+ android:enabled="false"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.telephony.VisualVoicemailService"/>
+ </intent-filter>
+
+ </service>
+
<activity android:name="android.telephony.cts.StubDialerActvity">
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
diff --git a/tests/tests/telephony/src/android/telephony/cts/PermissionlessVisualVoicemailService.java b/tests/tests/telephony/src/android/telephony/cts/PermissionlessVisualVoicemailService.java
new file mode 100644
index 0000000..ca6c18f
--- /dev/null
+++ b/tests/tests/telephony/src/android/telephony/cts/PermissionlessVisualVoicemailService.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 android.telephony.cts;
+
+/**
+ * Same as {@link MockVisualVoicemailService} but does not require {@link
+ * android.Manifest.permission.BIND_VISUAL_VOICEMAIL_SERVICE} in the manifest and should never be
+ * bound by the system.
+ */
+public class PermissionlessVisualVoicemailService extends MockVisualVoicemailService{
+
+}
diff --git a/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java b/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java
index 62b13b1..829cc2d 100644
--- a/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java
@@ -18,6 +18,7 @@
import android.app.Instrumentation;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -36,6 +37,7 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import android.telephony.VisualVoicemailService;
import android.telephony.VisualVoicemailSms;
@@ -89,6 +91,14 @@
.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
mPhoneNumber = telecomManager.getLine1Number(mPhoneAccountHandle);
}
+
+ PackageManager packageManager = mContext.getPackageManager();
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(mContext, MockVisualVoicemailService.class),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(mContext, PermissionlessVisualVoicemailService.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}
@Override
@@ -105,6 +115,55 @@
super.tearDown();
}
+ public void testPermissionlessService_ignored() {
+ if (!hasTelephony(mContext)) {
+ Log.d(TAG, "skipping test that requires telephony feature");
+ return;
+ }
+
+ PackageManager packageManager = mContext.getPackageManager();
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(mContext, MockVisualVoicemailService.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(mContext, PermissionlessVisualVoicemailService.class),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+ String clientPrefix = "//CTSVVM";
+ String text = "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1";
+
+ VisualVoicemailService.setSmsFilterSettings(mContext, mPhoneAccountHandle,
+ new VisualVoicemailSmsFilterSettings.Builder()
+ .setClientPrefix(clientPrefix)
+ .build());
+
+ try {
+ VisualVoicemailService
+ .sendVisualVoicemailSms(mContext, mPhoneAccountHandle, mPhoneNumber, (short) 0,
+ text, null);
+ fail("SecurityException expected");
+ } catch (SecurityException e){
+ // Expected
+ }
+
+ CompletableFuture<VisualVoicemailSms> future = new CompletableFuture<>();
+ PermissionlessVisualVoicemailService.setSmsFuture(future);
+
+ setupSmsReceiver();
+
+ SmsManager.getDefault().sendTextMessage(mPhoneNumber, null, text, null, null);
+
+ mSmsReceiver.assertReceived(EVENT_RECEIVED_TIMEOUT_MILLIS);
+ try {
+ future.get(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ throw new RuntimeException("Unexpected visual voicemail SMS received");
+ } catch (TimeoutException e) {
+ // expected
+ } catch (ExecutionException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
public void testFilter() {
if (!hasTelephony(mContext)) {
Log.d(TAG, "skipping test that requires telephony feature");
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index 4fc8bc8..3a1e41c 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -376,7 +376,7 @@
public void testSaveLayerClippedWithAlpha() {
// verify that renderer can draw nested clipped layers with different alpha
createTest() // picture mode is disable due to bug:34871089
- .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+ .addCanvasClient((canvas, width, height) -> {
Paint redPaint = new Paint();
redPaint.setColor(0xffff0000);
canvas.saveLayerAlpha(40, 5, 80, 70, 0x7f, Canvas.CLIP_TO_LAYER_SAVE_FLAG);
@@ -392,7 +392,7 @@
public void testSaveLayerUnclippedWithAlpha() {
// verify that renderer can draw nested unclipped layers with different alpha
createTest() // picture mode is disable due to bug:34871089
- .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+ .addCanvasClient((canvas, width, height) -> {
Paint redPaint = new Paint();
redPaint.setColor(0xffff0000);
canvas.saveLayerAlpha(40, 5, 80, 70, 0x7f, 0);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DisplayModifier.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DisplayModifier.java
index 99a9ef6..2de4e34 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DisplayModifier.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DisplayModifier.java
@@ -17,6 +17,8 @@
import android.graphics.Canvas;
import android.graphics.ColorFilter;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -326,6 +328,22 @@
put(PORTERDUFF_MODES[i].toString(),
new XfermodeModifier(PORTERDUFF_MODES[i]));
}
+ put("lowSaturationColorMatrix", new DisplayModifier() {
+ @Override
+ public void modifyDrawing(Paint paint, Canvas canvas) {
+ ColorMatrix matrix = new ColorMatrix();
+ matrix.setSaturation(0.1f);
+ paint.setColorFilter(new ColorMatrixColorFilter(matrix));
+ }
+ });
+ put("highSaturationColorMatrix", new DisplayModifier() {
+ @Override
+ public void modifyDrawing(Paint paint, Canvas canvas) {
+ ColorMatrix matrix = new ColorMatrix();
+ matrix.setSaturation(10.0f);
+ paint.setColorFilter(new ColorMatrixColorFilter(matrix));
+ }
+ });
}
});
diff --git a/tests/tests/util/src/android/util/cts/ArrayMapTest.java b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
index ec17516..c89972d 100644
--- a/tests/tests/util/src/android/util/cts/ArrayMapTest.java
+++ b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
@@ -16,6 +16,11 @@
package android.util.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.os.Bundle;
@@ -31,10 +36,14 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.AbstractMap;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Set;
@SmallTest
@@ -535,4 +544,131 @@
}
checkEntrySetToArray(testMap);
}
+
+ @Test
+ public void testCanNotIteratePastEnd_entrySetIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Set<Map.Entry<String, String>> expectedEntriesToIterate = new HashSet<>(Arrays.asList(
+ entryOf("key 1", "value 1"),
+ entryOf("key 2", "value 2")
+ ));
+ Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
+
+ // Assert iteration over the expected two entries in any order
+ assertTrue(iterator.hasNext());
+ Map.Entry<String, String> firstEntry = copyOf(iterator.next());
+ assertTrue(expectedEntriesToIterate.remove(firstEntry));
+
+ assertTrue(iterator.hasNext());
+ Map.Entry<String, String> secondEntry = copyOf(iterator.next());
+ assertTrue(expectedEntriesToIterate.remove(secondEntry));
+
+ assertFalse(iterator.hasNext());
+
+ try {
+ iterator.next();
+ fail();
+ } catch (NoSuchElementException expected) {
+ }
+ }
+
+ private static<K, V> Map.Entry<K, V> entryOf(K key, V value) {
+ return new AbstractMap.SimpleEntry<>(key, value);
+ }
+
+ private static<K, V> Map.Entry<K, V> copyOf(Map.Entry<K, V> entry) {
+ return entryOf(entry.getKey(), entry.getValue());
+ }
+
+ @Test
+ public void testCanNotIteratePastEnd_keySetIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Set<String> expectedKeysToIterate = new HashSet<>(Arrays.asList("key 1", "key 2"));
+ Iterator<String> iterator = map.keySet().iterator();
+
+ // Assert iteration over the expected two keys in any order
+ assertTrue(iterator.hasNext());
+ String firstKey = iterator.next();
+ assertTrue(expectedKeysToIterate.remove(firstKey));
+
+ assertTrue(iterator.hasNext());
+ String secondKey = iterator.next();
+ assertTrue(expectedKeysToIterate.remove(secondKey));
+
+ assertFalse(iterator.hasNext());
+
+ try {
+ iterator.next();
+ fail();
+ } catch (NoSuchElementException expected) {
+ }
+ }
+
+ @Test
+ public void testCanNotIteratePastEnd_valuesIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Set<String> expectedValuesToIterate = new HashSet<>(Arrays.asList("value 1", "value 2"));
+ Iterator<String> iterator = map.values().iterator();
+
+ // Assert iteration over the expected two values in any order
+ assertTrue(iterator.hasNext());
+ String firstValue = iterator.next();
+ assertTrue(expectedValuesToIterate.remove(firstValue));
+
+ assertTrue(iterator.hasNext());
+ String secondValue = iterator.next();
+ assertTrue(expectedValuesToIterate.remove(secondValue));
+
+ assertFalse(iterator.hasNext());
+
+ try {
+ iterator.next();
+ fail();
+ } catch (NoSuchElementException expected) {
+ }
+ }
+
+ /**
+ * The entrySet Iterator returns itself from each call to {@code next()}.
+ * This is unusual behavior for {@link Iterator#next()}; this test ensures that
+ * any future change to this behavior is deliberate.
+ */
+ @Test
+ public void testUnusualBehavior_eachEntryIsSameAsIterator_entrySetIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
+
+ assertSame(iterator, iterator.next());
+ assertSame(iterator, iterator.next());
+ }
+
+ @Test
+ public void testUnusualBehavior_equalsThrowsAfterRemove_entrySetIterator() {
+ Map<String, String> map = new ArrayMap<>();
+ map.put("key 1", "value 1");
+ map.put("key 2", "value 2");
+ Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
+ iterator.next();
+ iterator.remove();
+ try {
+ iterator.equals(iterator);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ private static<T> void assertEqualsBothWays(T a, T b) {
+ assertEquals(a, b);
+ assertEquals(b, a);
+ assertEquals(a.hashCode(), b.hashCode());
+ }
+
}
diff --git a/tests/tests/util/src/android/util/cts/ArraySetTest.java b/tests/tests/util/src/android/util/cts/ArraySetTest.java
index 554d209..f8ff037 100644
--- a/tests/tests/util/src/android/util/cts/ArraySetTest.java
+++ b/tests/tests/util/src/android/util/cts/ArraySetTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -34,6 +35,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.NoSuchElementException;
// As is the case with ArraySet itself, ArraySetTest borrows heavily from ArrayMapTest.
@@ -513,4 +515,22 @@
Object[] objectArray = arraySet.toArray();
compareArraySetAndRawArray(arraySet, objectArray);
}
+
+ @Test
+ public void testCanNotIteratePastEnd() {
+ ArraySet<String> set = new ArraySet<>();
+ set.add("value");
+ Iterator<String> iterator = set.iterator();
+
+ assertTrue(iterator.hasNext());
+ assertEquals("value", iterator.next());
+ assertFalse(iterator.hasNext());
+
+ try {
+ iterator.next();
+ fail();
+ } catch (NoSuchElementException expected) {
+ }
+ }
+
}
diff --git a/tests/tests/view/src/android/view/cts/AbsSavedStateTest.java b/tests/tests/view/src/android/view/cts/AbsSavedStateTest.java
index ba781cf..12bba10 100644
--- a/tests/tests/view/src/android/view/cts/AbsSavedStateTest.java
+++ b/tests/tests/view/src/android/view/cts/AbsSavedStateTest.java
@@ -56,7 +56,7 @@
@Test
public void testConstructor() {
AbsSavedState superState = new AbsSavedStateImpl(Parcel.obtain());
- assertNull(superState.getSuperState());
+ assertSame(AbsSavedState.EMPTY_STATE, superState.getSuperState());
AbsSavedState s = new AbsSavedStateImpl(superState);
assertSame(superState, s.getSuperState());
@@ -64,22 +64,21 @@
Parcel source = Parcel.obtain();
source.writeParcelable(superState, 0);
source.setDataPosition(0);
- s = new AbsSavedStateImpl(source);
+ s = new AbsSavedStateImpl(source, AbsSavedStateImpl.class.getClassLoader());
assertTrue(s.getSuperState() instanceof AbsSavedState);
source = Parcel.obtain();
s = new AbsSavedStateImpl(source);
assertSame(AbsSavedState.EMPTY_STATE, s.getSuperState());
- ClassLoader loader = AbsSavedState.class.getClassLoader();
source = Parcel.obtain();
source.writeParcelable(superState, 0);
source.setDataPosition(0);
- s = new AbsSavedStateImpl(source, loader);
+ s = new AbsSavedStateImpl(source, AbsSavedStateImpl.class.getClassLoader());
assertTrue(s.getSuperState() instanceof AbsSavedState);
source = Parcel.obtain();
- s = new AbsSavedStateImpl(source, loader);
+ s = new AbsSavedStateImpl(source, AbsSavedState.class.getClassLoader());
assertSame(AbsSavedState.EMPTY_STATE, s.getSuperState());
}
@@ -99,17 +98,20 @@
parcel.setDataPosition(0);
AbsSavedState unparceled = AbsSavedState.CREATOR.createFromParcel(parcel);
assertNotNull(unparceled);
- assertEquals(AbsSavedState.EMPTY_STATE, unparceled.getSuperState());
+ assertNull(unparceled.getSuperState());
AbsSavedState stateWithSuper = new AbsSavedStateImpl(state);
parcel = Parcel.obtain();
stateWithSuper.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
- try {
- AbsSavedState.CREATOR.createFromParcel(parcel);
- fail("Expected IllegalStateException");
- } catch (IllegalStateException e) {
- // Expected.
+ if (AbsSavedState.CREATOR instanceof Parcelable.ClassLoaderCreator) {
+ try {
+ ((Parcelable.ClassLoaderCreator) AbsSavedState.CREATOR).createFromParcel(parcel,
+ AbsSavedStateImpl.class.getClassLoader());
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
}
}
@@ -136,13 +138,19 @@
super(source, loader);
}
- public static final Creator<AbsSavedStateImpl> CREATOR = new Creator<AbsSavedStateImpl>() {
+ public static final Creator<AbsSavedStateImpl> CREATOR =
+ new ClassLoaderCreator<AbsSavedStateImpl>() {
@Override
public AbsSavedStateImpl createFromParcel(Parcel source) {
return new AbsSavedStateImpl(source);
}
@Override
+ public AbsSavedStateImpl createFromParcel(Parcel source, ClassLoader loader) {
+ return new AbsSavedStateImpl(source, loader);
+ }
+
+ @Override
public AbsSavedStateImpl[] newArray(int size) {
return new AbsSavedStateImpl[size];
}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
index dc15f17..871e880 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertThat;
+import android.os.LocaleList;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -41,6 +42,8 @@
@RunWith(AndroidJUnit4.class)
public class TextClassificationManagerTest {
+ private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
+
private TextClassificationManager mTcm;
private TextClassifier mClassifier;
@@ -55,15 +58,15 @@
public void testSmartSelection() {
if (isTextClassifierDisabled()) return;
- String text = "Contact me at droid@email.com";
+ String text = "Contact me at droid@android.com";
String selected = "droid";
- String suggested = "droid@email.com";
+ String suggested = "droid@android.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),
+ assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
}
@@ -79,7 +82,7 @@
int smartStartIndex = text.indexOf(suggested);
int smartEndIndex = smartStartIndex + suggested.length();
- assertThat(mClassifier.suggestSelection(text, startIndex, endIndex),
+ assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_URL));
}
@@ -87,11 +90,11 @@
public void testTextClassificationResult() {
if (isTextClassifierDisabled()) return;
- String text = "Contact me at droid@email.com";
- String classifiedText = "droid@email.com";
+ String text = "Contact me at droid@android.com";
+ String classifiedText = "droid@android.com";
int startIndex = text.indexOf(classifiedText);
int endIndex = startIndex + classifiedText.length();
- assertThat(mClassifier.getTextClassificationResult(text, startIndex, endIndex),
+ assertThat(mClassifier.getTextClassificationResult(text, startIndex, endIndex, LOCALES),
isTextClassificationResult(classifiedText, TextClassifier.TYPE_EMAIL));
}
@@ -103,7 +106,7 @@
String classifiedText = "http://www.android.com";
int startIndex = text.indexOf(classifiedText);
int endIndex = startIndex + classifiedText.length();
- assertThat(mClassifier.getTextClassificationResult(text, startIndex, endIndex),
+ assertThat(mClassifier.getTextClassificationResult(text, startIndex, endIndex, LOCALES),
isTextClassificationResult(classifiedText, TextClassifier.TYPE_URL));
}
diff --git a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
index 9b03467..68aaa08 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -1261,10 +1261,19 @@
int measuredWidth = popupRoot.getMeasuredWidth();
int measuredHeight = popupRoot.getMeasuredHeight();
View anchor = mActivity.findViewById(R.id.anchor_middle);
+
+ // The popup should occupy all available vertical space.
int[] anchorLocationInWindowXY = new int[2];
anchor.getLocationInWindow(anchorLocationInWindowXY);
+ assertEquals(measuredHeight,
+ parentHeight - (anchorLocationInWindowXY[1] + anchor.getHeight()));
- assertEquals(measuredHeight, parentHeight - anchorLocationInWindowXY[1]);
+ // The popup should be vertically aligned to the anchor's bottom edge.
+ int[] anchorLocationOnScreenXY = new int[2];
+ anchor.getLocationOnScreen(anchorLocationOnScreenXY);
+ int[] popupLocationOnScreenXY = new int[2];
+ popupRoot.getLocationOnScreen(popupLocationOnScreenXY);
+ assertEquals(anchorLocationOnScreenXY[1] + anchor.getHeight(), popupLocationOnScreenXY[1]);
}
@Test
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index eae7235..4fe7ded 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -7008,10 +7008,10 @@
initTextViewForTypingOnUiThread();
TextClassifier mockClassifier = mock(TextClassifier.class);
when(mockClassifier.suggestSelection(
- any(CharSequence.class), anyInt(), anyInt()))
+ any(CharSequence.class), anyInt(), anyInt(), any(LocaleList.class)))
.thenReturn(new TextSelection.Builder(SMARTSELECT_START, SMARTSELECT_END).build());
when(mockClassifier.getTextClassificationResult(
- any(CharSequence.class), anyInt(), anyInt()))
+ any(CharSequence.class), anyInt(), anyInt(), any(LocaleList.class)))
.thenReturn(new TextClassificationResult.Builder().build());
mActivityRule.runOnUiThread(() -> {
mTextView.setTextIsSelectable(true);