Merge "Revert "NdkMediaCodec: CTS for surface APIs""
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index d817b1a..119d9c9 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1779,17 +1779,6 @@
android:value="android.hardware.type.watch:android.software.leanback" />
</activity>
- <activity android:name=".notifications.PackagePriorityVerifierActivity"
- android:label="@string/package_priority_test">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.cts.intent.category.MANUAL_TEST" />
- </intent-filter>
- <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
- <meta-data android:name="test_excluded_features"
- android:value="android.hardware.type.watch:android.software.leanback" />
- </activity>
-
<service android:name=".notifications.MockListener"
android:exported="true"
android:label="@string/nls_service_name"
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index 9d9739d..ffe29d2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -105,7 +105,7 @@
Build.FINGERPRINT, Build.ID, Build.MANUFACTURER, Build.MODEL, Build.PRODUCT,
referenceFingerprint, Build.SERIAL, Build.TAGS, Build.TYPE, versionBaseOs,
Build.VERSION.RELEASE, Integer.toString(Build.VERSION.SDK_INT),
- versionSecurityPatch);
+ versionSecurityPatch, Build.VERSION.INCREMENTAL);
// add device properties to the result with a prefix tag for each key
for (Entry<String, String> entry :
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 1456228d..79295d8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -88,7 +88,6 @@
tests.add(new EnableHintsTest());
tests.add(new SnoozeTest());
tests.add(new UnsnoozeTest());
- tests.add(new EnableHintsTest());
tests.add(new MessageBundleTest());
tests.add(new IsDisabledTest());
tests.add(new ServiceStoppedTest());
@@ -186,6 +185,9 @@
@Override
void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
deleteChannel();
}
@@ -215,6 +217,14 @@
}
@Override
+ void setUp() {
+ createChannel();
+ sendNotifications();
+ status = READY;
+ delay();
+ }
+
+ @Override
void test() {
MockListener.probeListenerPayloads(mContext,
new MockListener.StringListResultCatcher() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
deleted file mode 100644
index 150c21f..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.notifications;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import com.android.cts.verifier.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests that the notification ranker honors user preferences about package priority.
- * Users can, in Settings, specify a package as being high priority. This should
- * result in the notificaitons from that package being ranked higher than those from
- * other packages.
- */
-public class PackagePriorityVerifierActivity
- extends InteractiveVerifierActivity {
- private static final String ACTION_POST = "com.android.cts.robot.ACTION_POST";
- private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
- private static final String EXTRA_ID = "ID";
- private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
- private static final String NOTIFICATION_CHANNEL_ID = "PackagePriorityVerifierActivity";
- static final String NOTIFICATION_BOT_PACKAGE = "com.android.cts.robot";
- private CharSequence mAppLabel;
-
- @Override
- int getTitleResource() {
- return R.string.package_priority_test;
- }
-
- @Override
- int getInstructionsResource() {
- return R.string.package_priority_info;
- }
-
- // Test Setup
-
- @Override
- protected List<InteractiveTestCase> createTestItems() {
- mAppLabel = getString(R.string.app_name);
- List<InteractiveTestCase> tests = new ArrayList<>(17);
- tests.add(new CheckForBot());
- tests.add(new IsEnabledTest());
- tests.add(new ServiceStartedTest());
- tests.add(new WaitForSetPriorityDefault());
- tests.add(new DefaultOrderTest());
- tests.add(new WaitForSetPriorityHigh());
- tests.add(new PackagePriorityOrderTest());
- return tests;
- }
-
- private void createChannel() {
- NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
- NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
- mNm.createNotificationChannel(channel);
- }
-
- private void deleteChannel() {
- mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
- }
-
- // Tests
-
- /** Make sure the helper package is installed. */
- protected class CheckForBot extends InteractiveTestCase {
- @Override
- View inflate(ViewGroup parent) {
- return createAutoItem(parent, R.string.package_priority_bot);
- }
-
- @Override
- void test() {
- PackageManager pm = mContext.getPackageManager();
- try {
- pm.getPackageInfo(NOTIFICATION_BOT_PACKAGE, 0);
- status = PASS;
- } catch (PackageManager.NameNotFoundException e) {
- status = FAIL;
- logFail("You must install the CTS Robot helper, aka " + NOTIFICATION_BOT_PACKAGE);
- }
- next();
- }
- }
-
- /** Wait for the user to set the target package priority to default. */
- protected class WaitForSetPriorityDefault extends InteractiveTestCase {
- @Override
- View inflate(ViewGroup parent) {
- return createRetryItem(parent, R.string.package_priority_default, mAppLabel);
- }
-
- @Override
- void setUp() {
- Log.i("WaitForSetPriorityDefault", "waiting for user");
- status = WAIT_FOR_USER;
- }
-
- @Override
- void test() {
- status = PASS;
- next();
- }
-
- @Override
- void tearDown() {
- MockListener.resetListenerData(mContext);
- delay();
- }
- }
-
- /** Wait for the user to set the target package priority to high. */
- protected class WaitForSetPriorityHigh extends InteractiveTestCase {
- @Override
- View inflate(ViewGroup parent) {
- return createRetryItem(parent, R.string.package_priority_high, mAppLabel);
- }
-
- @Override
- void setUp() {
- Log.i("WaitForSetPriorityHigh", "waiting for user");
- status = WAIT_FOR_USER;
- }
-
- @Override
- void test() {
- status = PASS;
- next();
- }
-
- @Override
- void tearDown() {
- MockListener.resetListenerData(mContext);
- delay();
- }
- }
-
- /**
- * With default priority, the notifcations should be reverse-ordered by time.
- * A is before B, and therefor should B should rank before A.
- */
- protected class DefaultOrderTest extends InteractiveTestCase {
- @Override
- View inflate(ViewGroup parent) {
- return createAutoItem(parent, R.string.attention_default_order);
- }
-
- @Override
- void setUp() {
- createChannel();
- sendNotifications();
- status = READY;
- // wait for notifications to move through the system
- delay();
- }
-
- @Override
- void test() {
- MockListener.probeListenerOrder(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> orderedKeys) {
- int rankA = indexOfPackageInKeys(orderedKeys, getPackageName());
- int rankB = indexOfPackageInKeys(orderedKeys, NOTIFICATION_BOT_PACKAGE);
- if (rankB != -1 && rankB < rankA) {
- status = PASS;
- } else {
- logFail("expected rankA (" + rankA + ") > rankB (" + rankB + ")");
- status = FAIL;
- }
- next();
- }
- });
- delay(); // in case the catcher never returns
- }
-
- @Override
- void tearDown() {
- cancelNotifications();
- deleteChannel();
- MockListener.resetListenerData(mContext);
- delay();
- }
- }
-
- /**
- * With higher package priority, A should rank above B.
- */
- protected class PackagePriorityOrderTest extends InteractiveTestCase {
- @Override
- View inflate(ViewGroup parent) {
- return createAutoItem(parent, R.string.package_priority_user_order);
- }
-
- @Override
- void setUp() {
- createChannel();
- sendNotifications();
- status = READY;
- // wait for notifications to move through the system
- delay();
- }
-
- @Override
- void test() {
- MockListener.probeListenerOrder(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> orderedKeys) {
- int rankA = indexOfPackageInKeys(orderedKeys, getPackageName());
- int rankB = indexOfPackageInKeys(orderedKeys, NOTIFICATION_BOT_PACKAGE);
- if (rankA != -1 && rankA < rankB) {
- status = PASS;
- } else {
- logFail("expected rankA (" + rankA + ") < rankB (" + rankB + ")");
- status = FAIL;
- }
- next();
- }
- });
- delay(); // in case the catcher never returns
- }
-
- @Override
- void tearDown() {
- cancelNotifications();
- deleteChannel();
- MockListener.resetListenerData(mContext);
- delay();
- }
- }
- // Utilities
-
- private void sendNotifications() {
- // post ours first, with an explicit time in the past to avoid any races.
- Notification.Builder alice = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
- .setContentTitle("alice title")
- .setContentText("alice content")
- .setSmallIcon(R.drawable.ic_stat_alice)
- .setWhen(System.currentTimeMillis() - 10000L)
- .setPriority(Notification.PRIORITY_DEFAULT);
- mNm.notify(0, alice.build());
-
- // then post theirs, so it should be higher by default due to recency
- Notification.Builder bob = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
- .setContentTitle("bob title")
- .setContentText("bob content")
- .setSmallIcon(android.R.drawable.stat_notify_error) // must be global resource
- .setWhen(System.currentTimeMillis())
- .setPriority(Notification.PRIORITY_DEFAULT);
- Intent postIntent = new Intent(ACTION_POST);
- postIntent.setPackage(NOTIFICATION_BOT_PACKAGE);
- postIntent.putExtra(EXTRA_ID, 0);
- postIntent.putExtra(EXTRA_NOTIFICATION, bob.build());
- postIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- sendBroadcast(postIntent);
- }
-
- private void cancelNotifications() {
- //cancel ours
- mNm.cancelAll();
- //cancel theirs
- Intent cancelIntent = new Intent(ACTION_CANCEL);
- cancelIntent.setPackage(NOTIFICATION_BOT_PACKAGE);
- cancelIntent.putExtra(EXTRA_ID, 0);
- cancelIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- sendBroadcast(cancelIntent);
- }
-
- /** Search a list of notification keys for a given packageName. */
- private int indexOfPackageInKeys(List<String> orderedKeys, String packageName) {
- for (int i = 0; i < orderedKeys.size(); i++) {
- if (orderedKeys.get(i).contains(packageName)) {
- return i;
- }
- }
- return -1;
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ShortcutThrottlingResetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ShortcutThrottlingResetActivity.java
index 313ebfa..6a97f0c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ShortcutThrottlingResetActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ShortcutThrottlingResetActivity.java
@@ -43,8 +43,7 @@
public class ShortcutThrottlingResetActivity extends InteractiveVerifierActivity {
private static final String TAG = "ShortcutThrottlingReset";
- private static final String NOTIFICATION_BOT_PACKAGE
- = PackagePriorityVerifierActivity.NOTIFICATION_BOT_PACKAGE;
+ private static final String NOTIFICATION_BOT_PACKAGE = "com.android.cts.robot";
private static final String ACTION_RESET_SETUP_NOTIFICATION =
"com.android.cts.robot.ACTION_RESET_SETUP_NOTIFICATION";
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
index b10b2b5..6237c23 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
@@ -61,6 +61,7 @@
private static final String VERSION_RELEASE = "ro.build.version.release";
private static final String VERSION_SDK = "ro.build.version.sdk";
private static final String VERSION_SECURITY_PATCH = "ro.build.version.security_patch";
+ private static final String VERSION_INCREMENTAL = "ro.build.version.incremental";
private static final String PREFIX_TAG = "cts:build_";
@@ -94,7 +95,7 @@
DevicePropertyInfo devicePropertyInfo = new DevicePropertyInfo(ABI, ABI2, ABIS, ABIS_32,
ABIS_64, BOARD, BRAND, DEVICE, FINGERPRINT, ID, MANUFACTURER, MODEL, PRODUCT,
REFERENCE_FINGERPRINT, SERIAL, TAGS, TYPE, VERSION_BASE_OS, VERSION_RELEASE,
- VERSION_SDK, VERSION_SECURITY_PATCH);
+ VERSION_SDK, VERSION_SECURITY_PATCH, VERSION_INCREMENTAL);
// add device properties to the result with a prefix tag for each key
for (Entry<String, String> entry :
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
index 1931a1a..b85a036 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
@@ -15,6 +15,7 @@
*/
package com.android.compatibility.common.tradefed.presubmit;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -28,7 +29,10 @@
import java.io.File;
import java.io.FilenameFilter;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -46,6 +50,14 @@
*/
private String MODULE_EXCEPTION = "CtsSplitApp";
+ private static Set<String> BINARY_EXCEPTIONS = new HashSet<>();
+ static {
+ /**
+ * This binary is a host side helper, so we do not need to check it.
+ */
+ BINARY_EXCEPTIONS.add("sepolicy-analyze");
+ }
+
/**
* Test that all apks have the same supported abis.
* Sometimes, if a module is missing LOCAL_MULTILIB := both, we will end up with only one of
@@ -102,5 +114,57 @@
}
}
- // TODO: add a test for test binary
+ /**
+ * Test that when CTS has multiple abis, we have binary for each ABI. In this case the abi will
+ * be the same with different bitness (only case supported by build system).
+ * <p/>
+ * If there is only one bitness, then we check that it's the right one.
+ */
+ @Test
+ public void testBinariesAbis() {
+ String ctsRoot = System.getProperty("CTS_ROOT");
+ File testcases = new File(ctsRoot, "/android-cts/testcases/");
+ if (!testcases.exists()) {
+ fail(String.format("%s does not exist", testcases));
+ return;
+ }
+ String[] listBinaries = testcases.list(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ if (name.contains(".")) {
+ return false;
+ }
+ if (BINARY_EXCEPTIONS.contains(name)) {
+ return false;
+ }
+ File file = new File(dir, name);
+ if (file.isDirectory()) {
+ return false;
+ }
+ if (!file.canExecute()) {
+ return false;
+ }
+ return true;
+ }
+ });
+ assertTrue(listBinaries.length > 0);
+ List<String> orderedList = Arrays.asList(listBinaries);
+ // we sort to have binary starting with same name, next to each other. The last two
+ // characters of their name with be the bitness (32 or 64).
+ Collections.sort(orderedList);
+ Set<String> buildTarget = AbiUtils.getAbisForArch(SuiteInfo.TARGET_ARCH);
+ // We expect one binary per abi of CTS, they should be appended with 32 or 64
+ for (int i = 0; i < orderedList.size(); i=i + buildTarget.size()) {
+ List<String> subSet = orderedList.subList(i, i + buildTarget.size());
+ if (subSet.size() > 1) {
+ String base = subSet.get(0).substring(0, subSet.get(0).length() - 2);
+ for (int j = 0; j < subSet.size(); j++) {
+ assertEquals(base, subSet.get(j).substring(0, subSet.get(j).length() - 2));
+ }
+ } else {
+ String bitness = AbiUtils.getBitness(buildTarget.iterator().next());
+ assertTrue(subSet.get(i).endsWith(bitness));
+ }
+ }
+ }
}
diff --git a/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java b/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java
index ec24b42..04ab61e 100644
--- a/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java
+++ b/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java
@@ -50,12 +50,13 @@
private final String mVersionRelease;
private final String mVersionSdk;
private final String mVersionSecurityPatch;
+ private final String mVersionIncremental;
public DevicePropertyInfo(String abi, String abi2, String abis, String abis32, String abis64,
String board, String brand, String device, String fingerprint, String id,
String manufacturer, String model, String product, String referenceFigerprint,
String serial, String tags, String type, String versionBaseOs, String versionRelease,
- String versionSdk, String versionSecurityPatch) {
+ String versionSdk, String versionSecurityPatch, String versionIncremental) {
mAbi = abi;
mAbi2 = abi2;
mAbis = abis;
@@ -77,6 +78,7 @@
mVersionRelease = versionRelease;
mVersionSdk = versionSdk;
mVersionSecurityPatch = versionSecurityPatch;
+ mVersionIncremental = versionIncremental;
}
/**
@@ -108,6 +110,7 @@
propertyMap.put(prefix + "version_release", mVersionRelease);
propertyMap.put(prefix + "version_sdk", mVersionSdk);
propertyMap.put(prefix + "version_security_patch", mVersionSecurityPatch);
+ propertyMap.put(prefix + "version_incremental", mVersionIncremental);
return propertyMap;
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index ddc98c2..5e7c9a7 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -40,7 +40,7 @@
private IAbi mAbi;
private IBuildInfo mCtsBuild;
- private static int[] sUsers;
+ private int[] mUsers;
@Override
public void setAbi(IAbi abi) {
@@ -56,6 +56,18 @@
protected void setUp() throws Exception {
super.setUp();
+ mUsers = Utils.createUsersForTest(getDevice());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ Utils.removeUsersForTest(getDevice(), mUsers);
+ mUsers = null;
+ }
+
+ private void prepareTestApps() throws Exception {
getDevice().uninstallPackage(PKG_STATS);
getDevice().uninstallPackage(PKG_A);
getDevice().uninstallPackage(PKG_B);
@@ -65,103 +77,101 @@
assertNull(getDevice().installPackage(buildHelper.getTestFile(APK_A), false));
assertNull(getDevice().installPackage(buildHelper.getTestFile(APK_B), false));
- if (sUsers != null) {
- for (int user : sUsers) {
- getDevice().executeShellCommand("appops set --user " + user + " " + PKG_STATS
- + " android:get_usage_stats allow");
- }
+ for (int user : mUsers) {
+ getDevice().executeShellCommand("appops set --user " + user + " " + PKG_STATS
+ + " android:get_usage_stats allow");
}
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- getDevice().uninstallPackage(PKG_STATS);
- getDevice().uninstallPackage(PKG_A);
- getDevice().uninstallPackage(PKG_B);
+ public void testEverything() throws Exception {
+ prepareTestApps(); doVerifyAppStats();
+ prepareTestApps(); doVerifyAppQuota();
+ prepareTestApps(); doVerifyAppAllocate();
+ prepareTestApps(); doVerifySummary();
+ prepareTestApps(); doVerifyStats();
+ prepareTestApps(); doVerifyStatsMultiple();
+ prepareTestApps(); doVerifyStatsExternal();
+ prepareTestApps(); doVerifyStatsExternalConsistent();
+ prepareTestApps(); doVerifyCategory();
+ prepareTestApps(); doCacheClearing();
}
- public void testA() throws Exception {
- sUsers = Utils.createUsersForTest(getDevice());
- }
-
- public void testVerifyAppStats() throws Exception {
- for (int user : sUsers) {
+ public void doVerifyAppStats() throws Exception {
+ for (int user : mUsers) {
runDeviceTests(PKG_A, CLASS, "testAllocate", user);
}
// TODO: remove this once 34723223 is fixed
getDevice().executeShellCommand("sync");
- for (int user : sUsers) {
+ for (int user : mUsers) {
runDeviceTests(PKG_A, CLASS, "testVerifySpaceManual", user);
runDeviceTests(PKG_A, CLASS, "testVerifySpaceApi", user);
}
}
- public void testVerifyAppQuota() throws Exception {
- for (int user : sUsers) {
+ public void doVerifyAppQuota() throws Exception {
+ for (int user : mUsers) {
runDeviceTests(PKG_A, CLASS, "testVerifyQuotaApi", user);
}
}
- public void testVerifyAppAllocate() throws Exception {
- for (int user : sUsers) {
+ public void doVerifyAppAllocate() throws Exception {
+ for (int user : mUsers) {
runDeviceTests(PKG_A, CLASS, "testVerifyAllocateApi", user);
}
}
- public void testVerifySummary() throws Exception {
- for (int user : sUsers) {
+ public void doVerifySummary() throws Exception {
+ for (int user : mUsers) {
runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifySummary", user);
}
}
- public void testVerifyStats() throws Exception {
- for (int user : sUsers) {
+ public void doVerifyStats() throws Exception {
+ for (int user : mUsers) {
runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStats", user);
}
}
- public void testVerifyStatsMultiple() throws Exception {
- for (int user : sUsers) {
+ public void doVerifyStatsMultiple() throws Exception {
+ for (int user : mUsers) {
runDeviceTests(PKG_A, CLASS, "testAllocate", user);
runDeviceTests(PKG_A, CLASS, "testAllocate", user);
runDeviceTests(PKG_B, CLASS, "testAllocate", user);
}
- for (int user : sUsers) {
+ for (int user : mUsers) {
runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsMultiple", user);
}
}
- public void testVerifyStatsExternal() throws Exception {
- for (int user : sUsers) {
+ public void doVerifyStatsExternal() throws Exception {
+ for (int user : mUsers) {
runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternal", user);
}
}
- public void testVerifyStatsExternalConsistent() throws Exception {
- for (int user : sUsers) {
+ public void doVerifyStatsExternalConsistent() throws Exception {
+ for (int user : mUsers) {
runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternalConsistent", user);
}
}
- public void testVerifyCategory() throws Exception {
- for (int user : sUsers) {
+ public void doVerifyCategory() throws Exception {
+ for (int user : mUsers) {
runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyCategory", user);
}
}
- public void testCacheClearing() throws Exception {
+ public void doCacheClearing() throws Exception {
// To make the cache clearing logic easier to verify, ignore any cache
// and low space reserved space.
getDevice().executeShellCommand("settings put global sys_storage_threshold_max_bytes 0");
getDevice().executeShellCommand("settings put global sys_storage_cache_max_bytes 0");
try {
- for (int user : sUsers) {
+ for (int user : mUsers) {
// Clear all other cached data to give ourselves a clean slate
getDevice().executeShellCommand("pm trim-caches 4096G");
runDeviceTests(PKG_STATS, CLASS_STATS, "testCacheClearing", user);
@@ -172,11 +182,6 @@
}
}
- public void testZ() throws Exception {
- Utils.removeUsersForTest(getDevice(), sUsers);
- sUsers = null;
- }
-
public void runDeviceTests(String packageName, String testClassName, String testMethodName,
int userId) throws DeviceNotAvailableException {
Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId);
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 15197a0..4d3812c 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
@@ -92,6 +92,7 @@
mDevice.waitForIdle();
// Set a PIN for this user
+ mDevice.executeShellCommand("settings put global require_password_to_decrypt 0");
mDevice.executeShellCommand("locksettings set-pin 12345");
}
@@ -105,6 +106,7 @@
// Clear PIN for this user
mDevice.executeShellCommand("locksettings clear --old 12345");
+ mDevice.executeShellCommand("settings delete global require_password_to_decrypt");
}
public void doBootCountBefore() throws Exception {
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
index ea0f034..65c8918 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
@@ -16,13 +16,9 @@
package com.android.cts.storageapp;
-import static com.android.cts.storageapp.Utils.MB_IN_BYTES;
-import static com.android.cts.storageapp.Utils.makeUniqueFile;
-
import android.content.Context;
import android.system.Os;
import android.system.OsConstants;
-import android.system.StructStat;
import android.util.Log;
import junit.framework.AssertionFailedError;
@@ -78,7 +74,11 @@
}
public static void assertMostlyEquals(long expected, long actual) {
- if (Math.abs(expected - actual) > 500 * KB_IN_BYTES) {
+ assertMostlyEquals(expected, actual, 500 * KB_IN_BYTES);
+ }
+
+ public static void assertMostlyEquals(long expected, long actual, long delta) {
+ if (Math.abs(expected - actual) > delta) {
throw new AssertionFailedError("Expected roughly " + expected + " but was " + actual
+ " [" + android.os.Process.myUserHandle() + "]");
}
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
index 48f2e77..8b60b93 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
@@ -216,9 +216,12 @@
// Apps using up some cache space shouldn't change how much we can
// allocate, or how much we think is free; but it should decrease real
// disk space.
- assertMostlyEquals(beforeAllocatable, sm.getAllocatableBytes(filesDir, 0));
- assertMostlyEquals(beforeFree, stats.getFreeBytes(null));
- assertMostlyEquals(targetA + targetB, beforeRaw - filesDir.getUsableSpace());
+ assertMostlyEquals(beforeAllocatable, sm.getAllocatableBytes(filesDir, 0),
+ 10 * MB_IN_BYTES);
+ assertMostlyEquals(beforeFree, stats.getFreeBytes(null),
+ 10 * MB_IN_BYTES);
+ assertMostlyEquals(targetA + targetB, beforeRaw - filesDir.getUsableSpace(),
+ 10 * MB_IN_BYTES);
assertMostlyEquals(targetA, getCacheBytes(PKG_A, user));
assertMostlyEquals(targetB, getCacheBytes(PKG_B, user));
@@ -229,7 +232,7 @@
sm.allocateBytes(filesDir, clear1, 0);
assertMostlyEquals(targetA, getCacheBytes(PKG_A, user));
- assertMostlyEquals(targetB / 2, getCacheBytes(PKG_B, user));
+ assertMostlyEquals(targetB / 2, getCacheBytes(PKG_B, user), 2 * MB_IN_BYTES);
// Allocate some more space for ourselves, which should now start
// trimming away at older app. Since we pivot between the two apps once
@@ -238,8 +241,8 @@
final long clear2 = filesDir.getUsableSpace() + (targetB / 2);
sm.allocateBytes(filesDir, clear2, 0);
- assertMostlyEquals(targetA / 2, getCacheBytes(PKG_A, user));
- assertMostlyEquals(targetA / 2, getCacheBytes(PKG_B, user));
+ assertMostlyEquals(targetA / 2, getCacheBytes(PKG_A, user), 2 * MB_IN_BYTES);
+ assertMostlyEquals(targetA / 2, getCacheBytes(PKG_B, user), 2 * MB_IN_BYTES);
}
private long getCacheBytes(String pkg, UserHandle user) {
@@ -250,6 +253,7 @@
private long doAllocate(String pkg, double fraction, long time) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final Intent intent = new Intent();
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.setComponent(new ComponentName(pkg, UtilsReceiver.class.getName()));
intent.putExtra(UtilsReceiver.EXTRA_FRACTION, fraction);
intent.putExtra(UtilsReceiver.EXTRA_TIME, time);
diff --git a/hostsidetests/devicepolicy/app/ContactDirectoryProvider/src/com/android/cts/contactdirectoryprovider/DirectoryProvider.java b/hostsidetests/devicepolicy/app/ContactDirectoryProvider/src/com/android/cts/contactdirectoryprovider/DirectoryProvider.java
index b46f013..9131304 100644
--- a/hostsidetests/devicepolicy/app/ContactDirectoryProvider/src/com/android/cts/contactdirectoryprovider/DirectoryProvider.java
+++ b/hostsidetests/devicepolicy/app/ContactDirectoryProvider/src/com/android/cts/contactdirectoryprovider/DirectoryProvider.java
@@ -33,6 +33,7 @@
import android.os.ParcelFileDescriptor;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Directory;
+import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -42,6 +43,7 @@
import java.util.concurrent.TimeUnit;
public class DirectoryProvider extends ContentProvider {
+ private static final String TAG = "DirectoryProvider";
private static final String CONFIG_NAME = "config";
private static final String SET_CUSTOM_PREFIX = "set_prefix";
private static final String AUTHORITY = "com.android.cts.contact.directory.provider";
@@ -113,6 +115,7 @@
final MatrixCursor cursor = new MatrixCursor(projection);
final AccountManager am = getContext().getSystemService(AccountManager.class);
Account[] accounts = am.getAccountsByType(TEST_ACCOUNT_TYPE);
+ Log.i(TAG, "Query GAL directories account size: " + accounts.length);
if (accounts != null) {
for (Account account : accounts) {
final Object[] row = new Object[projection.length];
@@ -205,6 +208,7 @@
// Set custom display name, so primary directory and corp directory will have different
// display name
if (method.equals(SET_CUSTOM_PREFIX)) {
+ Log.i(TAG, "Set directory name prefix: " + arg);
mSharedPrefs.edit().putString(SET_CUSTOM_PREFIX, arg).apply();
// Force update the content in CP2
final long token = Binder.clearCallingIdentity();
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
index 109804a..ae6282f 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -29,7 +29,7 @@
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner compatibility-device-util \
ub-uiautomator android-support-test guava
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java
index 65adee8..1ca6402 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java
@@ -27,6 +27,7 @@
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
@@ -529,37 +530,40 @@
boolean hasPrimaryDirectory = false;
boolean hasManagedDirectory = false;
- while(cursor.moveToNext()) {
- final long directoryId = cursor.getLong(0);
- if (directoryId == Directory.DEFAULT) {
- hasPrimaryDefault = true;
- } else if (directoryId == Directory.LOCAL_INVISIBLE) {
- hasPrimaryInvisible = true;
- } else if (directoryId == Directory.ENTERPRISE_DEFAULT) {
- hasManagedDefault = true;
- } else if (directoryId == Directory.ENTERPRISE_LOCAL_INVISIBLE) {
- hasManagedInvisible = true;
- } else {
- final String displayName = cursor.getString(1);
- if (Directory.isEnterpriseDirectoryId(directoryId)
- && displayName.equals(MANAGED_DIRECTORY_NAME)) {
- hasManagedDirectory = true;
- }
- if (!Directory.isEnterpriseDirectoryId(directoryId)
- && displayName.equals(PRIMARY_DIRECTORY_NAME)) {
- hasPrimaryDirectory = true;
+ try {
+ while(cursor.moveToNext()) {
+ final long directoryId = cursor.getLong(0);
+ if (directoryId == Directory.DEFAULT) {
+ hasPrimaryDefault = true;
+ } else if (directoryId == Directory.LOCAL_INVISIBLE) {
+ hasPrimaryInvisible = true;
+ } else if (directoryId == Directory.ENTERPRISE_DEFAULT) {
+ hasManagedDefault = true;
+ } else if (directoryId == Directory.ENTERPRISE_LOCAL_INVISIBLE) {
+ hasManagedInvisible = true;
+ } else {
+ final String displayName = cursor.getString(1);
+ if (Directory.isEnterpriseDirectoryId(directoryId)
+ && displayName.equals(MANAGED_DIRECTORY_NAME)) {
+ hasManagedDirectory = true;
+ }
+ if (!Directory.isEnterpriseDirectoryId(directoryId)
+ && displayName.equals(PRIMARY_DIRECTORY_NAME)) {
+ hasPrimaryDirectory = true;
+ }
}
}
- }
- cursor.close();
-
- if (i + 1 == MAX_RETRY_DIRECTORY_QUERY) {
- assertTrue(hasPrimaryDefault);
- assertTrue(hasPrimaryInvisible);
- assertTrue(hasManagedDefault);
- assertTrue(hasManagedInvisible);
- assertTrue(hasPrimaryDirectory);
- assertTrue(hasManagedDirectory);
+ if (i + 1 == MAX_RETRY_DIRECTORY_QUERY) {
+ DatabaseUtils.dumpCursor(cursor);
+ assertTrue(hasPrimaryDefault);
+ assertTrue(hasPrimaryInvisible);
+ assertTrue(hasManagedDefault);
+ assertTrue(hasManagedInvisible);
+ assertTrue(hasPrimaryDirectory);
+ assertTrue(hasManagedDirectory);
+ }
+ } finally {
+ cursor.close();
}
if (hasPrimaryDefault && hasPrimaryInvisible && hasManagedDefault
&& hasManagedInvisible && hasPrimaryDirectory && hasManagedDirectory) {
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/RingtoneSyncTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/RingtoneSyncTest.java
index 76dc75b..ec8c09e 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/RingtoneSyncTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/RingtoneSyncTest.java
@@ -16,6 +16,8 @@
package com.android.cts.managedprofile;
+import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS;
+
import android.content.ContentResolver;
import android.content.Context;
import android.media.RingtoneManager;
@@ -27,16 +29,13 @@
*/
public class RingtoneSyncTest extends BaseManagedProfileTest {
- private ContentResolver mContentResolver;
-
- // TODO: Expose this as SystemApi in android.provider.Settings
- private final String SETTING_SYNC_PARENT_SOUNDS = "sync_parent_sounds";
-
private static final int[] RINGTONE_TYPES = {
RingtoneManager.TYPE_RINGTONE, RingtoneManager.TYPE_NOTIFICATION,
RingtoneManager.TYPE_ALARM
};
+ private ContentResolver mContentResolver;
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -53,7 +52,7 @@
*/
public void testRingtoneSync() throws Exception {
// Managed profile was just created, so sync should be active by default
- assertEquals(1, Settings.Secure.getInt(mContentResolver, SETTING_SYNC_PARENT_SOUNDS));
+ assertEquals(1, Settings.Secure.getInt(mContentResolver, SYNC_PARENT_SOUNDS));
String defaultRingtone = Settings.System.getString(mContentResolver,
Settings.System.RINGTONE);
@@ -86,14 +85,14 @@
assertTrue(Settings.System.canWrite(mContext));
// Explicitly set a work sound, to stop syncing ringtones between profiles.
- assertEquals(1, Settings.Secure.getInt(mContentResolver, SETTING_SYNC_PARENT_SOUNDS));
+ assertEquals(1, Settings.Secure.getInt(mContentResolver, SYNC_PARENT_SOUNDS));
try {
RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, null);
- assertEquals(0, Settings.Secure.getInt(mContentResolver, SETTING_SYNC_PARENT_SOUNDS));
+ assertEquals(0, Settings.Secure.getInt(mContentResolver, SYNC_PARENT_SOUNDS));
validateRingtoneManagerGetRingtone(null, ringtoneType);
} finally {
// Reset the setting we just changed.
- Settings.Secure.putInt(mContentResolver, SETTING_SYNC_PARENT_SOUNDS, 1);
+ Settings.Secure.putInt(mContentResolver, SYNC_PARENT_SOUNDS, 1);
}
// After re-unifying, the uri should be the same as the parent's uri.
@@ -103,10 +102,10 @@
// Manually disabling sync again, without changing settings, should put the ringtone uri
// back to its earlier value of null.
try {
- Settings.Secure.putInt(mContentResolver, SETTING_SYNC_PARENT_SOUNDS, 0);
+ Settings.Secure.putInt(mContentResolver, SYNC_PARENT_SOUNDS, 0);
assertNull(RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType));
} finally {
- Settings.Secure.putInt(mContentResolver, SETTING_SYNC_PARENT_SOUNDS, 1);
+ Settings.Secure.putInt(mContentResolver, SYNC_PARENT_SOUNDS, 1);
}
}
diff --git a/hostsidetests/inputmethodservice/common/Android.mk b/hostsidetests/inputmethodservice/common/Android.mk
new file mode 100644
index 0000000..83bc34d
--- /dev/null
+++ b/hostsidetests/inputmethodservice/common/Android.mk
@@ -0,0 +1,50 @@
+# 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.
+
+###############################################################################
+# Build the common library for use device-side
+###############################################################################
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_LIBRARIES := junit
+
+LOCAL_MODULE_TAGS := tests
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MODULE := CtsInputMethodServiceCommon
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+###############################################################################
+# Build the common library for use host-side
+###############################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_LIBRARIES := junit-host
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE := cts-inputmethodservice-common-host
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ComponentNameUtils.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ComponentNameUtils.java
new file mode 100644
index 0000000..507e84a
--- /dev/null
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ComponentNameUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.inputmethodservice.cts.common;
+
+/**
+ * Utility class to build Android's component name.
+ */
+final class ComponentNameUtils {
+
+ // This is utility class, can't instantiate.
+ private ComponentNameUtils() {}
+
+ /**
+ * Build Android component name from {@code packageName} and {@code className}.
+ * @param packageName package name of a component.
+ * @param className class name of a component.
+ * @return a component of {@code packageName/className} that can be used to specify component,
+ * for example, for {@code android.content.Intent}.
+ */
+ static String buildComponentName(final String packageName, final String className) {
+ return packageName + "/" + (className.startsWith(packageName)
+ ? className.substring(packageName.length()) : className);
+ }
+}
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
new file mode 100644
index 0000000..9bb4464
--- /dev/null
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.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.inputmethodservice.cts.common;
+
+/**
+ * Constants of device event.
+ */
+public final class DeviceEventConstants {
+
+ // This is constants holding class, can't instantiate.
+ private DeviceEventConstants() {}
+
+ /** Intent action in order to record IME events. */
+ public static final String ACTION_DEVICE_EVENT =
+ "android.inputmethodservice.cts.action.DEVICE_EVENT";
+
+ /**
+ * Intent receiver's package, class, and component name.
+ */
+ public static final String RECEIVER_PACKAGE = "android.inputmethodservice.cts.provider";
+ public static final String RECEIVER_CLASS =
+ "android.inputmethodservice.cts.receiver.EventReceiver";
+ public static final String RECEIVER_COMPONENT = ComponentNameUtils.buildComponentName(
+ RECEIVER_PACKAGE, RECEIVER_CLASS);
+
+ /**
+ * Intent extra key for who sends a device event.
+ * Values are Input Method class name, for example {@link Ime1Constants#CLASS}, or device test
+ * method name {@code testClassName#testMethodName}.
+ *
+ * @see android.content.Intent#putExtra(String,String)
+ * @see android.content.Intent#getStringExtra(String)
+ */
+ public static final String EXTRA_EVENT_SENDER = "event_sender";
+
+ /**
+ * Intent extra key for what type a device event is. Values are {@link DeviceEventType#name()}.
+ *
+ * @see android.content.Intent#putExtra(String,String)
+ * @see android.content.Intent#getStringExtra(String)
+ */
+ public static final String EXTRA_EVENT_TYPE = "event_type";
+
+ /**
+ * Intent extra key for at what time a device event happens. Value is taken from
+ * {@code android.os.SystemClock.uptimeMillis()}.
+ *
+ * @see android.content.Intent#putExtra(String,long)
+ * @see android.content.Intent#getLongExtra(String,long)
+ */
+ public static final String EXTRA_EVENT_TIME = "event_time";
+
+ /**
+ * Types of device event, a value of {@link #EXTRA_EVENT_TYPE}.
+ */
+ public enum DeviceEventType {
+ /**
+ * {@link android.inputmethodservice.InputMethodService#onCreate() onCreate()} callback.
+ */
+ ON_CREATE,
+
+ /**
+ * {@link android.inputmethodservice.InputMethodService#onStartInput(android.view.inputmethod.EditorInfo,boolean) onStartInput(EditorInfo,boolean}
+ * callback.
+ */
+ ON_START_INPUT,
+
+ /**
+ * {@link android.inputmethodservice.InputMethodService#onStartInputView(android.view.inputmethod.EditorInfo, boolean) onStartInputView(EditorInfo,boolean}
+ */
+ ON_START_INPUT_VIEW,
+
+ /**
+ * {@link android.inputmethodservice.InputMethodService#onFinishInputView(boolean) onFinishInputView(boolean)}
+ * callback.
+ */
+ ON_FINISH_INPUT_VIEW,
+
+ /**
+ * {@link android.inputmethodservice.InputMethodService#onFinishInput() onFinishInput()}
+ * callback.
+ */
+ ON_FINISH_INPUT,
+
+ /**
+ * {@link android.inputmethodservice.InputMethodService#onDestroy() onDestroy()} callback.
+ */
+ ON_DESTROY,
+
+ /** Test start and end event types. */
+ TEST_START,
+ TEST_END,
+ }
+}
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
new file mode 100644
index 0000000..172e1a7
--- /dev/null
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
@@ -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.
+ */
+
+package android.inputmethodservice.cts.common;
+
+/**
+ * Constants of CtsInputMethodServiceEventProvider.apk that keeps IME event records.
+ */
+public final class EventProviderConstants {
+
+ // This is constants holding class, can't instantiate.
+ private EventProviderConstants() {}
+
+ /** Package name of the IME event provider. */
+ public static final String PACKAGE = "android.inputmethodservice.cts.provider";
+
+ /** Class name of IME event provider. */
+ public static final String CLASS =
+ "android.inputmethodservice.cts.provider.EventProviderConstants";
+
+ /** APK file name. */
+ public static final String APK = "CtsInputMethodServiceEventProvider.apk";
+
+ /** The authority of IME event provider. */
+ public static final String AUTHORITY = "android.inputmethodservice.cts.provider";
+
+ /** The base URI of IME event provider. */
+ private static final String BASE_URI = "content://" + AUTHORITY;
+
+ /**
+ * The Android platform's base MIME type for a content: URI containing a Cursor of a single
+ * item. Copied from android.content.ContentResolver.CURSOR_ITEM_BASE_TYPE.
+ */
+ private static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
+
+ /**
+ * The Android platform's base MIME type for a content: URI containing a Cursor of zero or more
+ * items. Copied from android.content.ContentResolver.CURSOR_DIR_BASE_TYPE.
+ */
+ private static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
+
+ /** Constants of Events table of IME event provider. */
+ public static final class EventTableConstants {
+
+ // This is constants holding class, can't instantiate.
+ private EventTableConstants() {}
+
+ /** Name of the table in content provider and database. */
+ public static final String NAME = "events";
+
+ /** Column name of the table that holds who sends an event. */
+ public static final String SENDER = "sender";
+
+ /** Column name of the table that holds what type of event is. */
+ public static final String TYPE = "type";
+
+ /** Column name of the table that holds when an event happens. */
+ public static final String TIME = "time";
+
+ /** Content URI of the table. */
+ public static final String CONTENT_URI = BASE_URI + "/" + NAME;
+
+ /** MIME type of a cursor of zero or more items of the table. */
+ public static final String TYPE_DIR =
+ CURSOR_DIR_BASE_TYPE + "/" + AUTHORITY + ".ime_event";
+
+ /** MIME tye of a cursor of a single item of the table. */
+ public static final String TYPE_ITEM =
+ CURSOR_ITEM_BASE_TYPE + "/" + AUTHORITY + ".ime_event";
+ }
+}
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime1Constants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime1Constants.java
new file mode 100644
index 0000000..3dc6ff1
--- /dev/null
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime1Constants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.inputmethodservice.cts.common;
+
+public final class Ime1Constants {
+
+ // This is constants holding class, can't instantiate.
+ private Ime1Constants() {}
+
+ public static final String PACKAGE = "android.inputmethodservice.cts.ime1";
+ public static final String CLASS = "android.inputmethodservice.cts.ime1.CtsInputMethod1";
+ public static final String APK = "CtsInputMethod1.apk";
+
+ public static final String IME_ID = ComponentNameUtils.buildComponentName(PACKAGE, CLASS);
+}
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime2Constants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime2Constants.java
new file mode 100644
index 0000000..eeefae9
--- /dev/null
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime2Constants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.inputmethodservice.cts.common;
+
+public final class Ime2Constants {
+
+ // This is constants holding class, can't instantiate.
+ private Ime2Constants() {}
+
+ public static final String PACKAGE = "android.inputmethodservice.cts.ime2";
+ public static final String CLASS = "android.inputmethodservice.cts.ime2.CtsInputMethod2";
+ public static final String APK = "CtsInputMethod2.apk";
+
+ public static final String IME_ID = ComponentNameUtils.buildComponentName(PACKAGE, CLASS);
+}
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java
new file mode 100644
index 0000000..61d5fc9
--- /dev/null
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java
@@ -0,0 +1,61 @@
+/*
+ * 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.inputmethodservice.cts.common;
+
+/**
+ * Constants of IME command android.content.Intent.
+ */
+public final class ImeCommandConstants {
+
+ // This is constants holding class, can't instantiate.
+ private ImeCommandConstants() {}
+
+ /** Intent action in order to record IME events. */
+ public static final String ACTION_IME_COMMAND =
+ "android.inputmethodservice.cts.action.IME_COMMAND";
+
+ public static final String EXTRA_COMMAND = "command";
+
+ public static final String EXTRA_ARG_CHARSEQUENCE1 = "arg_charsequence1";
+ public static final String EXTRA_ARG_STRING1 = "arg_string1";
+ public static final String EXTRA_ARG_INT1 = "arg_int1";
+
+ /**
+ * This command has the mock IME call {@link android.view.inputmethod.InputConnection#commitText(CharSequence,int) InputConnection#commitText(CharSequence text, int newCursorPosition)}.
+ * <ul>
+ * <li>argument {@code text} needs to be specified by {@link #EXTRA_ARG_CHARSEQUENCE1}.</li>
+ * <li>argument {@code newCursorPosition} needs to be specified by {@link #EXTRA_ARG_INT1}.</li>
+ * </ul>
+ */
+ public static final String COMMAND_COMMIT_TEXT = "commitText";
+
+ /**
+ * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#switchInputMethod(String)} InputMethodService#switchInputMethod(String imeId)}.
+ * <ul>
+ * <li>argument {@code imeId} needs to be specified by {@link #EXTRA_ARG_STRING1}.</li>
+ * </ul>
+ */
+ public static final String COMMAND_SWITCH_INPUT_METHOD = "switchInputMethod";
+
+ /**
+ * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)} InputMethodService#requestHideSelf(int flags)}.
+ * <ul>
+ * <li>argument {@code flags} needs to be specified by {@link #EXTRA_ARG_INT1}.</li>
+ * </ul>
+ */
+ public static final String COMMAND_REQUEST_HIDE_SELF = "requestHideSelf";
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/Android.mk b/hostsidetests/inputmethodservice/deviceside/ime1/Android.mk
new file mode 100644
index 0000000..fe67791
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/Android.mk
@@ -0,0 +1,41 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+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_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIR := res
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ CtsInputMethodServiceCommon \
+ CtsInputMethodServiceLib
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsInputMethod1
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
new file mode 100755
index 0000000..70de83f
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?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.inputmethodservice.cts.ime1">
+
+ <!--
+ TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
+ latest OS behaviors.
+ -->
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
+
+ <application
+ android:label="@string/ime_name"
+ android:allowBackup="false"
+ android:theme="@android:style/Theme.InputMethod"
+ >
+ <service
+ android:name=".CtsInputMethod1"
+ android:label="@string/ime_name"
+ android:permission="android.permission.BIND_INPUT_METHOD">
+ <intent-filter>
+ <action android:name="android.view.InputMethod" />
+ </intent-filter>
+ <meta-data
+ android:name="android.view.im"
+ android:resource="@xml/ime1" />
+ </service>
+ </application>
+
+</manifest>
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/res/layout/input_view.xml b/hostsidetests/inputmethodservice/deviceside/ime1/res/layout/input_view.xml
new file mode 100644
index 0000000..c3c69e6
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/res/layout/input_view.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/holo_orange_dark"
+ android:padding="80dp"
+>
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:textSize="72sp"
+ android:gravity="center"
+ android:id="@+id/ime_name"
+ android:text="@string/ime_name" />
+</RelativeLayout>
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/res/values/colors.xml b/hostsidetests/inputmethodservice/deviceside/ime1/res/values/colors.xml
new file mode 100644
index 0000000..932b97b
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <color name="input_view_background">@android:color/holo_orange_dark</color>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/res/values/strings.xml b/hostsidetests/inputmethodservice/deviceside/ime1/res/values/strings.xml
new file mode 100644
index 0000000..324a330
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/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>
+ <string name="ime_name" translatable="false">IME1</string>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/res/xml/ime1.xml b/hostsidetests/inputmethodservice/deviceside/ime1/res/xml/ime1.xml
new file mode 100644
index 0000000..3cbef44
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/res/xml/ime1.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.
+-->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsSwitchingToNextInputMethod="true">
+</input-method>
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/src/android/inputmethodservice/cts/ime1/CtsInputMethod1.java b/hostsidetests/inputmethodservice/deviceside/ime1/src/android/inputmethodservice/cts/ime1/CtsInputMethod1.java
new file mode 100644
index 0000000..4176014
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/src/android/inputmethodservice/cts/ime1/CtsInputMethod1.java
@@ -0,0 +1,28 @@
+/*
+ * 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.inputmethodservice.cts.ime1;
+
+import android.inputmethodservice.cts.ime.CtsBaseInputMethod;
+import android.view.View;
+
+public final class CtsInputMethod1 extends CtsBaseInputMethod {
+
+ @Override
+ public View onCreateInputView() {
+ return getLayoutInflater().inflate(R.layout.input_view, null /* root */);
+ }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/Android.mk b/hostsidetests/inputmethodservice/deviceside/ime2/Android.mk
new file mode 100644
index 0000000..cf9cb37
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/Android.mk
@@ -0,0 +1,41 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+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_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIR := res
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ CtsInputMethodServiceCommon \
+ CtsInputMethodServiceLib
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsInputMethod2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
new file mode 100755
index 0000000..a166ba3
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?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.inputmethodservice.cts.ime2">
+
+ <!--
+ TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
+ latest OS behaviors.
+ -->
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
+
+ <application
+ android:label="@string/ime_name"
+ android:allowBackup="false"
+ android:theme="@android:style/Theme.InputMethod"
+ >
+ <service
+ android:name=".CtsInputMethod2"
+ android:label="@string/ime_name"
+ android:permission="android.permission.BIND_INPUT_METHOD">
+ <intent-filter>
+ <action android:name="android.view.InputMethod" />
+ </intent-filter>
+ <meta-data
+ android:name="android.view.im"
+ android:resource="@xml/ime2" />
+ </service>
+ </application>
+
+</manifest>
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/res/layout/input_view.xml b/hostsidetests/inputmethodservice/deviceside/ime2/res/layout/input_view.xml
new file mode 100644
index 0000000..ebd8810
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/res/layout/input_view.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/holo_blue_dark"
+ android:padding="80dp"
+>
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:textSize="72sp"
+ android:gravity="center"
+ android:id="@+id/ime_name"
+ android:text="@string/ime_name" />
+</RelativeLayout>
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/res/values/colors.xml b/hostsidetests/inputmethodservice/deviceside/ime2/res/values/colors.xml
new file mode 100644
index 0000000..903407a
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <color name="input_view_background">@android:color/holo_blue_dark</color>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/res/values/strings.xml b/hostsidetests/inputmethodservice/deviceside/ime2/res/values/strings.xml
new file mode 100644
index 0000000..436943f
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/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>
+ <string name="ime_name" translatable="false">IME2</string>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/res/xml/ime2.xml b/hostsidetests/inputmethodservice/deviceside/ime2/res/xml/ime2.xml
new file mode 100644
index 0000000..3cbef44
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/res/xml/ime2.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.
+-->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsSwitchingToNextInputMethod="true">
+</input-method>
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/src/android/inputmethodservice/cts/ime2/CtsInputMethod2.java b/hostsidetests/inputmethodservice/deviceside/ime2/src/android/inputmethodservice/cts/ime2/CtsInputMethod2.java
new file mode 100644
index 0000000..74a197d
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/src/android/inputmethodservice/cts/ime2/CtsInputMethod2.java
@@ -0,0 +1,28 @@
+/*
+ * 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.inputmethodservice.cts.ime2;
+
+import android.inputmethodservice.cts.ime.CtsBaseInputMethod;
+import android.view.View;
+
+public final class CtsInputMethod2 extends CtsBaseInputMethod {
+
+ @Override
+ public View onCreateInputView() {
+ return getLayoutInflater().inflate(R.layout.input_view, null /* root */);
+ }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/Android.mk b/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
index f5cb955..0223d0d 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
@@ -18,7 +18,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-annotations
+ android-support-annotations \
+ CtsInputMethodServiceCommon
LOCAL_MODULE_TAGS := tests
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
new file mode 100644
index 0000000..7c88c14
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
@@ -0,0 +1,225 @@
+/*
+ * 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.inputmethodservice.cts;
+
+import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TIME;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_CLASS;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_PACKAGE;
+
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.inputmethodservice.cts.common.DeviceEventConstants;
+import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
+import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
+import android.inputmethodservice.cts.db.Entity;
+import android.inputmethodservice.cts.db.Field;
+import android.inputmethodservice.cts.db.Table;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * Device event object.
+ * <p>Device event is one of IME event and Test event, and is used to test behaviors of Input Method
+ * Framework.</p>
+ */
+public final class DeviceEvent {
+
+ private static final boolean DEBUG_STREAM = false;
+
+ public static final Table<DeviceEvent> TABLE = new DeviceEventTable(EventTableConstants.NAME);
+
+ /**
+ * Create an intent to send a device event.
+ * @param sender an event sender.
+ * @param type an event type defined at {@link DeviceEventType}.
+ * @return an intent that has event {@code sender}, {@code type}, time from
+ * {@link SystemClock#uptimeMillis()}, and target component of event receiver.
+ */
+ public static Intent newDeviceEventIntent(@NonNull final String sender,
+ @NonNull final DeviceEventType type) {
+ return new Intent()
+ .setAction(ACTION_DEVICE_EVENT)
+ .setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
+ .putExtra(EXTRA_EVENT_SENDER, sender)
+ .putExtra(EXTRA_EVENT_TYPE, type.name())
+ .putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
+ }
+
+ /**
+ * Create an {@link DeviceEvent} object from an intent.
+ * @param intent a device event intent defined at {@link DeviceEventConstants}.
+ * @return {@link DeviceEvent} object that has event sender, type, and time form an
+ * {@code intent}.
+ */
+ public static DeviceEvent newEvent(final Intent intent) {
+ final String sender = intent.getStringExtra(EXTRA_EVENT_SENDER);
+ if (sender == null) {
+ throw new IllegalArgumentException(
+ "Intent must have " + EXTRA_EVENT_SENDER + ": " + intent);
+ }
+
+ final String typeString = intent.getStringExtra(EXTRA_EVENT_TYPE);
+ if (typeString == null) {
+ throw new IllegalArgumentException(
+ "Intent must have " + EXTRA_EVENT_TYPE + ": " + intent);
+ }
+ final DeviceEventType type = DeviceEventType.valueOf(typeString);
+
+ if (!intent.hasExtra(EXTRA_EVENT_TIME)) {
+ throw new IllegalArgumentException(
+ "Intent must have " + EXTRA_EVENT_TIME + ": " + intent);
+ }
+
+ return new DeviceEvent(sender, type, intent.getLongExtra(EXTRA_EVENT_TIME, 0L));
+ }
+
+ /**
+ * Build {@link ContentValues} object from {@link DeviceEvent} object.
+ * @param event a {@link DeviceEvent} object to be converted.
+ * @return a converted {@link ContentValues} object.
+ */
+ public static ContentValues buildContentValues(final DeviceEvent event) {
+ return TABLE.buildContentValues(event);
+ }
+
+ /**
+ * Build {@link Stream<DeviceEvent>} object from {@link Cursor} comes from Content Provider.
+ * @param cursor a {@link Cursor} object to be converted.
+ * @return a converted {@link Stream<DeviceEvent>} object.
+ */
+ public static Stream<DeviceEvent> buildStream(final Cursor cursor) {
+ return TABLE.buildStream(cursor);
+ }
+
+ /**
+ * Build {@link Predicate<DeviceEvent>} whether a device event comes from {@code sender}
+ *
+ * @param sender event sender.
+ * @return {@link Predicate<DeviceEvent>} object.
+ */
+ public static Predicate<DeviceEvent> isFrom(final String sender) {
+ return e -> e.sender.equals(sender);
+ }
+
+ /**
+ * Build {@link Predicate<DeviceEvent>} whether a device event has an event {@code type}.
+ *
+ * @param type a event type defined in {@link DeviceEventType}.
+ * @return {@link Predicate<DeviceEvent>} object.
+ */
+ public static Predicate<DeviceEvent> isType(final DeviceEventType type) {
+ return e -> e.type == type;
+ }
+
+ /**
+ * Build {@link Predicate<DeviceEvent>} whether a device event is newer than or equals to
+ * {@code time}.
+ *
+ * @param time a time to compare against.
+ * @return {@link Predicate<DeviceEvent>} object.
+ */
+ public static Predicate<DeviceEvent> isNewerThan(final long time) {
+ return e -> e.time >= time;
+ }
+
+ /**
+ * Event source, either Input Method class name testClass#testMethod.
+ */
+ @NonNull
+ public final String sender;
+
+ /**
+ * Event type, either IME event or Test event.
+ */
+ @NonNull
+ public final DeviceEventType type;
+
+ /**
+ * Event time, value is from {@link SystemClock#uptimeMillis()}.
+ */
+ public final long time;
+
+ private DeviceEvent(final String sender, final DeviceEventType type, final long time) {
+ this.sender = sender;
+ this.type = type;
+ this.time = time;
+ }
+
+ @Override
+ public String toString() {
+ return "Event{ time:" + time + " type:" + type + " sender:" + sender + " }";
+ }
+
+ /**
+ * Abstraction of device event table in database.
+ */
+ private static final class DeviceEventTable extends Table<DeviceEvent> {
+
+ private static final String LOG_TAG = DeviceEventTable.class.getSimpleName();
+
+ private final Field SENDER;
+ private final Field TYPE;
+ private final Field TIME;
+
+ private DeviceEventTable(final String name) {
+ super(name, new Entity.Builder<DeviceEvent>()
+ .addField(EventTableConstants.SENDER, Cursor.FIELD_TYPE_STRING)
+ .addField(EventTableConstants.TYPE, Cursor.FIELD_TYPE_STRING)
+ .addField(EventTableConstants.TIME, Cursor.FIELD_TYPE_INTEGER)
+ .build());
+ SENDER = getField(EventTableConstants.SENDER);
+ TYPE = getField(EventTableConstants.TYPE);
+ TIME = getField(EventTableConstants.TIME);
+ }
+
+ @Override
+ public ContentValues buildContentValues(final DeviceEvent event) {
+ final ContentValues values = new ContentValues();
+ SENDER.putString(values, event.sender);
+ TYPE.putString(values, event.type.name());
+ TIME.putLong(values, event.time);
+ return values;
+ }
+
+ @Override
+ public Stream<DeviceEvent> buildStream(Cursor cursor) {
+ if (DEBUG_STREAM) {
+ Log.d(LOG_TAG, "buildStream:");
+ }
+ final Stream.Builder<DeviceEvent> builder = Stream.builder();
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ final DeviceEvent event = new DeviceEvent(
+ SENDER.getString(cursor),
+ DeviceEventType.valueOf(TYPE.getString(cursor)),
+ TIME.getLong(cursor));
+ builder.accept(event);
+ if (DEBUG_STREAM) {
+ Log.d(LOG_TAG, " event=" +event);
+ }
+ }
+ return builder.build();
+ }
+ }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
new file mode 100644
index 0000000..6ae3568
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
@@ -0,0 +1,151 @@
+/*
+ * 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.inputmethodservice.cts.ime;
+
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT_VIEW;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT_VIEW;
+
+import android.content.Intent;
+import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.cts.DeviceEvent;
+import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
+import android.inputmethodservice.cts.ime.ImeCommandReceiver.ImeCommandCallbacks;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import java.util.function.Consumer;
+
+public abstract class CtsBaseInputMethod extends InputMethodService implements ImeCommandCallbacks {
+
+ protected static final boolean DEBUG = false;
+
+ private final ImeCommandReceiver<CtsBaseInputMethod> mImeCommandReceiver =
+ new ImeCommandReceiver<>();
+ private String mLogTag;
+
+ @Override
+ public void onCreate() {
+ mLogTag = getClass().getSimpleName();
+ if (DEBUG) {
+ Log.d(mLogTag, "onCreate:");
+ }
+ sendEvent(ON_CREATE);
+
+ super.onCreate();
+
+ mImeCommandReceiver.register(this /* ime */);
+ }
+
+ @Override
+ public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+ if (DEBUG) {
+ Log.d(mLogTag, "onStartInput:"
+ + " editorInfo=" + editorInfo
+ + " restarting=" + restarting);
+ }
+ sendEvent(ON_START_INPUT, editorInfo, restarting);
+
+ super.onStartInput(editorInfo, restarting);
+ }
+
+ @Override
+ public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+ if (DEBUG) {
+ Log.d(mLogTag, "onStartInputView:"
+ + " editorInfo=" + editorInfo
+ + " restarting=" + restarting);
+ }
+ sendEvent(ON_START_INPUT_VIEW, editorInfo, restarting);
+
+ super.onStartInputView(editorInfo, restarting);
+ }
+
+ @Override
+ public void onFinishInputView(boolean finishingInput) {
+ if (DEBUG) {
+ Log.d(mLogTag, "onFinishInputView: finishingInput=" + finishingInput);
+ }
+ sendEvent(ON_FINISH_INPUT_VIEW, finishingInput);
+
+ super.onFinishInputView(finishingInput);
+ }
+
+ @Override
+ public void onFinishInput() {
+ if (DEBUG) {
+ Log.d(mLogTag, "onFinishInput:");
+ }
+ sendEvent(ON_FINISH_INPUT);
+
+ super.onFinishInput();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) {
+ Log.d(mLogTag, "onDestroy:");
+ }
+ sendEvent(ON_DESTROY);
+
+ super.onDestroy();
+
+ unregisterReceiver(mImeCommandReceiver);
+ }
+
+ //
+ // Implementations of {@link ImeCommandCallbacks}.
+ //
+
+ @Override
+ public void commandCommitText(final CharSequence text, final int newCursorPosition) {
+ executeOnInputConnection(ic -> {
+ // TODO: Log the return value of {@link InputConnection#commitText(CharSequence,int)}.
+ ic.commitText(text, newCursorPosition);
+ });
+ }
+
+ @Override
+ public void commandSwitchInputMethod(final String imeId) {
+ switchInputMethod(imeId);
+ }
+
+ @Override
+ public void commandRequestHideSelf(final int flags) {
+ requestHideSelf(flags);
+ }
+
+ private void executeOnInputConnection(final Consumer<InputConnection> consumer) {
+ final InputConnection ic = getCurrentInputConnection();
+ // TODO: Check and log whether {@code ic} is null or equals to
+ // {@link #getCurrentInputBindin().getConnection()}.
+ if (ic != null) {
+ consumer.accept(ic);
+ }
+ }
+
+ private void sendEvent(final DeviceEventType type, final Object... args) {
+ final String sender = getClass().getName();
+ final Intent intent = DeviceEvent.newDeviceEventIntent(sender, type);
+ // TODO: Send arbitrary {@code args} in {@code intent}.
+ sendBroadcast(intent);
+ }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java
new file mode 100644
index 0000000..23e5555
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java
@@ -0,0 +1,110 @@
+/*
+ * 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.inputmethodservice.cts.ime;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.cts.common.ImeCommandConstants;
+import android.inputmethodservice.cts.ime.ImeCommandReceiver.ImeCommandCallbacks;
+import android.util.Log;
+
+/**
+ * {@link ImeCommandConstants#ACTION_IME_COMMAND} intent receiver.
+ */
+final class ImeCommandReceiver<T extends InputMethodService & ImeCommandCallbacks>
+ extends BroadcastReceiver {
+
+ private static final boolean DEBUG = false;
+
+ interface ImeCommandCallbacks {
+ /**
+ * Callback method for {@link ImeCommandConstants#COMMAND_COMMIT_TEXT} intent.
+ *
+ * @param text text to be committed via {@link android.view.inputmethod.InputConnection}.
+ * @param newCursorPosition new cursor position after commit.
+ */
+ void commandCommitText(final CharSequence text, final int newCursorPosition);
+
+ /**
+ * Callback method for {@link ImeCommandConstants#COMMAND_SWITCH_INPUT_METHOD} intent.
+ *
+ * @param imeId IME id to switch.
+ */
+ void commandSwitchInputMethod(final String imeId);
+
+ /**
+ * Callback method for {@link ImeCommandConstants#COMMAND_REQUEST_HIDE_SELF} intent.
+ */
+ void commandRequestHideSelf(final int flags);
+ }
+
+ private T mIme;
+
+ void register(final T ime) {
+ mIme = ime;
+ ime.registerReceiver(this, new IntentFilter(ImeCommandConstants.ACTION_IME_COMMAND));
+ }
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final String command = intent.getStringExtra(ImeCommandConstants.EXTRA_COMMAND);
+ if (DEBUG) {
+ Log.d(mIme.getClass().getSimpleName(), "onReceive: command=" + command);
+ }
+
+ switch (command) {
+ case ImeCommandConstants.COMMAND_COMMIT_TEXT: {
+ final CharSequence text = getCharSequence1(intent);
+ final int newCursorPosition = getInt1(intent);
+ mIme.commandCommitText(text, newCursorPosition);
+ return;
+ }
+ case ImeCommandConstants.COMMAND_SWITCH_INPUT_METHOD: {
+ final String imeId = getString1(intent);
+ mIme.commandSwitchInputMethod(imeId);
+ return;
+ }
+ case ImeCommandConstants.COMMAND_REQUEST_HIDE_SELF: {
+ final int flags = getInt1(intent);
+ mIme.commandRequestHideSelf(flags);
+ return;
+ }
+ default: {
+ throw new UnsupportedOperationException("Unknown IME command: " + command);
+ }
+ }
+ }
+
+ private static CharSequence getCharSequence1(final Intent intent) {
+ return intent.getCharSequenceExtra(ImeCommandConstants.EXTRA_ARG_CHARSEQUENCE1);
+ }
+
+ private static String getString1(final Intent intent) {
+ return intent.getStringExtra(ImeCommandConstants.EXTRA_ARG_STRING1);
+ }
+
+ private static int getInt1(final Intent intent) {
+ if (intent.hasExtra(ImeCommandConstants.EXTRA_ARG_INT1)) {
+ return intent.getIntExtra(ImeCommandConstants.EXTRA_ARG_INT1, 0);
+ }
+ throw new IllegalArgumentException(
+ "Needs " + ImeCommandConstants.EXTRA_ARG_INT1 + " in " + intent);
+ }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/Android.mk b/hostsidetests/inputmethodservice/deviceside/provider/Android.mk
new file mode 100644
index 0000000..03b3946
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/provider/Android.mk
@@ -0,0 +1,40 @@
+# 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_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ CtsInputMethodServiceCommon \
+ CtsInputMethodServiceLib
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsInputMethodServiceEventProvider
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
new file mode 100755
index 0000000..9189da3
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.inputmethodservice.cts.provider">
+
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
+
+ <application android:label="CtsInputMethodServiceEventProvider">
+ <provider
+ android:authorities="android.inputmethodservice.cts.provider"
+ android:name="android.inputmethodservice.cts.provider.EventProvider"
+ android:exported="true" />
+ <receiver android:name="android.inputmethodservice.cts.receiver.EventReceiver">
+ <intent-filter>
+ <action android:name="android.inputmethodservice.cts.action.IME_EVENT" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+</manifest>
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/EventProvider.java b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/EventProvider.java
new file mode 100644
index 0000000..46a32c6
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/EventProvider.java
@@ -0,0 +1,161 @@
+/*
+ * 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.inputmethodservice.cts.provider;
+
+import static android.inputmethodservice.cts.common.EventProviderConstants.AUTHORITY;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
+import android.inputmethodservice.cts.db.Database;
+import android.inputmethodservice.cts.db.Table;
+import android.inputmethodservice.cts.DeviceEvent;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * IME event content provider.
+ */
+public final class EventProvider extends ContentProvider {
+
+ private static final String TAG = EventProvider.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final String DB_NAME = "database";
+ private static final int DB_VERSION = 1;
+
+ private UriHelper.Factory mUriFactory;
+ private Database mDatabase;
+
+ @Override
+ public boolean onCreate() {
+ mUriFactory = UriHelper.Factory.builder()
+ .addUri(AUTHORITY, EventTableConstants.NAME, EventTableConstants.TYPE_DIR)
+ .addUri(AUTHORITY, EventTableConstants.NAME + "/#", EventTableConstants.TYPE_ITEM)
+ .build();
+ mDatabase = new Database(getContext(), DB_NAME, DB_VERSION) {
+ @Override
+ @NonNull
+ protected List<Table> getTables() {
+ return Collections.singletonList(DeviceEvent.TABLE);
+ }
+ };
+ return true;
+ }
+
+ @Override
+ public Cursor query(@NonNull final Uri uri, @Nullable final String[] projection,
+ final @Nullable String selection, @Nullable final String[] selectionArgs,
+ @Nullable final String orderBy) {
+ final UriHelper uriHelper = mUriFactory.newInstance(uri);
+ if (DEBUG) {
+ Log.d(TAG, "query:"
+ + " uri=" + uri
+ + " projection=" + Arrays.toString(projection)
+ + " selection=" + uriHelper.buildSelection(selection)
+ + " selectionArgs=" + Arrays.toString(
+ uriHelper.buildSelectionArgs(selectionArgs))
+ + " orderBy=" + orderBy);
+ }
+ final Cursor cursor = mDatabase.query(
+ uriHelper.table, projection, uriHelper.buildSelection(selection),
+ uriHelper.buildSelectionArgs(selectionArgs), orderBy);
+ if (DEBUG) {
+ Log.d(TAG, " query.count=" + cursor.getCount());
+ }
+ cursor.setNotificationUri(getContext().getContentResolver(), uri);
+ return cursor;
+ }
+
+ @Override
+ public Uri insert(@NonNull final Uri uri, @Nullable final ContentValues values) {
+ final UriHelper uriHelper = mUriFactory.newInstance(uri);
+ if (DEBUG) {
+ Log.d(TAG, "insert: uri=" + uri + " values={" + values + "}");
+ }
+ final long rowId = mDatabase.insert(uriHelper.table, values);
+ final Uri insertedUri = ContentUris.withAppendedId(uri, rowId);
+ if (DEBUG) {
+ Log.d(TAG, " insert.uri=" + insertedUri);
+ }
+ getContext().getContentResolver().notifyChange(insertedUri, null);
+ return insertedUri;
+ }
+
+ @Override
+ public int delete(@NonNull final Uri uri, @Nullable final String selection,
+ @Nullable final String[] selectionArgs) {
+ final UriHelper uriHelper = mUriFactory.newInstance(uri);
+ if (DEBUG) {
+ Log.d(TAG, "delete:"
+ + " uri=" + uri
+ + " selection=" + uriHelper.buildSelection(selection)
+ + " selectionArgs=" + Arrays.toString(
+ uriHelper.buildSelectionArgs(selectionArgs)));
+ }
+ final int count = mDatabase.delete(uriHelper.table, uriHelper.buildSelection(selection),
+ uriHelper.buildSelectionArgs(selectionArgs));
+ if (DEBUG) {
+ Log.d(TAG, " delete.count=" + count);
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+ return count;
+ }
+
+ @Override
+ public int update(@NonNull final Uri uri, @Nullable final ContentValues values,
+ final @Nullable String selection, @Nullable final String[] selectionArgs) {
+ final UriHelper uriHelper = mUriFactory.newInstance(uri);
+ if (DEBUG) {
+ Log.d(TAG, "update:"
+ + " uri=" + uri
+ + " values={" + values + "}"
+ + " selection=" + uriHelper.buildSelection(selection)
+ + " selectionArgs=" + Arrays.toString(
+ uriHelper.buildSelectionArgs(selectionArgs)));
+ }
+ final int count = mDatabase.update(uriHelper.table, values,
+ uriHelper.buildSelection(selection), uriHelper.buildSelectionArgs(selectionArgs));
+ if (DEBUG) {
+ Log.d(TAG, " update.count=" + count);
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+ return count;
+ }
+
+ @Override
+ @Nullable
+ public String getType(@NonNull final Uri uri) {
+ return mUriFactory.getTypeOf(uri);
+ }
+
+ @Override
+ public void shutdown() {
+ super.shutdown();
+ mDatabase.close();
+ mDatabase = null;
+ mUriFactory = null;
+ }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/UriHelper.java b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/UriHelper.java
new file mode 100644
index 0000000..1a4f945
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/UriHelper.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.cts.provider;
+
+import android.content.UriMatcher;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import java.util.List;
+
+/**
+ * Content URI helper.
+ *
+ * Helper object to parse content URI passed to content provider. A helper object is instantiated
+ * via {@link Factory#newInstance(Uri)}, and a {@link Factory} object should be instantiated using
+ * {@link FactoryBuilder}.
+ *
+ * A content URI is assumed to have a format "content://authority/table[/id]?" where table is a
+ * SQLite table name in content provider and id is a primary key.
+ */
+final class UriHelper {
+
+ static final class Factory {
+ private final UriMatcher mUriMatcher;
+ private final SparseArray<String> mUriTypeMap;
+
+ public static FactoryBuilder builder() {
+ return new FactoryBuilder();
+ }
+
+ private Factory(final FactoryBuilder builder) {
+ mUriMatcher = builder.mUriMatcher;
+ mUriTypeMap = builder.mUriTypeMap;
+ }
+
+ @NonNull
+ UriHelper newInstance(final Uri uri) {
+ if (mUriMatcher.match(uri) == UriMatcher.NO_MATCH) {
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ return new UriHelper(uri);
+ }
+
+ @Nullable
+ String getTypeOf(final Uri uri) {
+ return mUriTypeMap.get(mUriMatcher.match(uri), null);
+ }
+ }
+
+ static final class FactoryBuilder {
+ private final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ private final SparseArray<String> mUriTypeMap = new SparseArray<>();
+ private int mMatcherCode;
+
+ private FactoryBuilder() {
+ mMatcherCode = 0;
+ }
+
+ FactoryBuilder addUri(final String authority, final String path, final String type) {
+ if (TextUtils.isEmpty(authority)) {
+ throw new IllegalArgumentException("Authority must not be empty");
+ }
+ if (TextUtils.isEmpty(path)) {
+ throw new IllegalArgumentException("Path must not be empty");
+ }
+ final int matcherCode = mMatcherCode++;
+ mUriMatcher.addURI(authority, path, matcherCode);
+ mUriTypeMap.append(matcherCode, type);
+ return this;
+ }
+
+ Factory build() {
+ if (mMatcherCode == 0) {
+ throw new IllegalStateException("No URI is defined");
+ }
+ return new Factory(this);
+ }
+ }
+
+ /** Name of SQLite table specified by content uri. */
+ @NonNull
+ final String table;
+
+ /** Primary id that is specified by content uri. Null if not. */
+ @Nullable
+ private final String mId;
+
+ private UriHelper(final Uri uri) {
+ final List<String> segments = uri.getPathSegments();
+ table = segments.get(0);
+ mId = (segments.size() >= 2) ? segments.get(1) : null;
+ }
+
+ /**
+ * Composes selection SQL text from content uri and {@code selection} specified.
+ * When content uri has a primary key, it needs to be composed with a selection text specified
+ * as content provider parameter.
+ *
+ * @param selection selection text specified as a parameter to content provider.
+ * @return composed selection SQL text, null if no selection specified.
+ */
+ @Nullable
+ String buildSelection(@Nullable final String selection) {
+ if (mId == null) {
+ return selection;
+ }
+ // A primary key is specified by uri, so that selection should be at least "_id = ?".
+ final StringBuilder sb = new StringBuilder().append(BaseColumns._ID).append(" = ?");
+ if (selection != null) {
+ // Selection is also specified as a parameter to content provider, so that it should be
+ // appended with AND, such that "_id = ? AND (selection_text)".
+ sb.append(" AND (").append(selection).append(")");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Composes selection argument array from context uri and {@code selectionArgs} specified.
+ * When content uri has a primary key, it needs to be provided in a final selection argument
+ * array.
+ *
+ * @param selectionArgs selection argument array specified as a parameter to content provider.
+ * @return composed selection argument array, null if selection argument is unnecessary.
+ */
+ @Nullable
+ String[] buildSelectionArgs(@Nullable final String[] selectionArgs) {
+ if (mId == null) {
+ return selectionArgs;
+ }
+ // A primary key is specified by uri but not as a parameter to content provider, the primary
+ // key value should be the sole selection argument.
+ if (selectionArgs == null || selectionArgs.length == 0) {
+ return new String[]{ mId };
+ }
+ // Selection args are also specified as a parameter to content provider, the primary key
+ // value should be prepended to those selection args.
+ final String[] args = new String[selectionArgs.length + 1];
+ System.arraycopy(selectionArgs, 0, args, 1, selectionArgs.length);
+ args[0] = mId;
+ return args;
+ }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/receiver/EventReceiver.java b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/receiver/EventReceiver.java
new file mode 100644
index 0000000..9b79605
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/receiver/EventReceiver.java
@@ -0,0 +1,53 @@
+/*
+ * 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.inputmethodservice.cts.receiver;
+
+import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TIME;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.inputmethodservice.cts.DeviceEvent;
+import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.util.Log;
+
+public final class EventReceiver extends BroadcastReceiver {
+
+ private static final String TAG = EventReceiver.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final Uri CONTENT_URI = Uri.parse(EventTableConstants.CONTENT_URI);
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ // Since {@code intent} which comes from host has no
+ // {@link DeviceEventConstants#EXTRA_EVENT_TIME EXTRA_EVENT_TIME} extra, here we record the
+ // time.
+ if (!intent.hasExtra(EXTRA_EVENT_TIME)) {
+ intent.putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
+ }
+ final DeviceEvent event = DeviceEvent.newEvent(intent);
+ if (DEBUG) {
+ Log.d(TAG, "onReceive: event=" + event);
+ }
+ final ContentValues values = DeviceEvent.buildContentValues(event);
+ context.getContentResolver().insert(CONTENT_URI, values);
+ }
+}
diff --git a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
index f4ce1bd..3602076 100644
--- a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
+++ b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
@@ -47,6 +47,7 @@
"/dev/galcore",
"/dev/genlock", // b/9035217
"/dev/graphics/galcore",
+ "/dev/hwbinder", // b/30886151
"/dev/ion",
"/dev/kgsl-2d0", // b/11271533
"/dev/kgsl-2d1", // b/11271533
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
index 5f6baea..ed75f73 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
@@ -67,6 +67,12 @@
if (extras.getBoolean("finish")) {
finish();
}
+ if (extras.getBoolean("moveToBack")) {
+ moveTaskToBack(true);
+ }
+ if (extras.containsKey("orientation")) {
+ setRequestedOrientation(extras.getInt("orientation"));
+ }
if (extras.getBoolean("dismissKeyguard")) {
getWindow().addFlags(FLAG_DISMISS_KEYGUARD);
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
index eb08861..567cbbb 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
@@ -22,6 +22,7 @@
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.util.Log;
public class TestActivity extends AbstractLifecycleLogActivity {
@@ -57,7 +58,9 @@
protected void onResume() {
super.onResume();
registerReceiver(mReceiver, new IntentFilter(ACTION_FINISH_SELF));
- dumpDisplaySize(getResources().getConfiguration());
+ final Configuration config = getResources().getConfiguration();
+ dumpDisplaySize(config);
+ dumpConfiguration(config);
}
@Override
@@ -70,6 +73,11 @@
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
dumpDisplaySize(newConfig);
+ dumpConfiguration(newConfig);
+ }
+
+ private void dumpConfiguration(Configuration config) {
+ Log.i(getTag(), "Configuration: " + config);
}
@Override
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
index 52b3055..c338b4d 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
@@ -32,6 +32,8 @@
private static final String LANDSCAPE_ACTIVITY_NAME = "LandscapeOrientationActivity";
private static final String NIGHT_MODE_ACTIVITY = "NightModeActivity";
+ private static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
+
/**
* Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity
* has an updated size when the Activity is resized from fullscreen to docked state.
@@ -215,6 +217,58 @@
}
/**
+ * Test that device handles moving between two tasks with different orientations.
+ */
+ public void testTaskCloseRestoreOrientation() throws Exception {
+ // Start landscape activity.
+ launchActivity(LANDSCAPE_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
+ assertEquals("Fullscreen app requested landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+
+ // Start another activity in a different task.
+ launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
+
+ // Request portrait
+ executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
+ mAmWmState.waitForRotation(mDevice, 1);
+
+ // Finish activity
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+ // Verify that activity brought to front is in originally requested orientation.
+ mAmWmState.waitForValidState(mDevice, LANDSCAPE_ACTIVITY_NAME);
+ assertEquals("Should return to app in landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+ }
+
+ /**
+ * Test that device handles moving between two tasks with different orientations.
+ */
+ public void testTaskMoveToBackOrientation() throws Exception {
+ // Start landscape activity.
+ launchActivity(LANDSCAPE_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
+ assertEquals("Fullscreen app requested landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+
+ // Start another activity in a different task.
+ launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
+
+ // Request portrait
+ executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
+ mAmWmState.waitForRotation(mDevice, 1);
+
+ // Finish activity
+ executeShellCommand(MOVE_TASK_TO_BACK_BROADCAST);
+
+ // Verify that activity brought to front is in originally requested orientation.
+ mAmWmState.waitForValidState(mDevice, LANDSCAPE_ACTIVITY_NAME);
+ assertEquals("Should return to app in landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+ }
+
+ /**
* Test that device doesn't change device orientation by app request while in multi-window.
*/
public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
@@ -354,7 +408,6 @@
final String windowName = getWindowName(activityName);
mAmWmState.computeState(mDevice, new String[] {activityName});
-
mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
index d1edd76..ede2b87 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
@@ -16,6 +16,11 @@
package android.server.cts;
+import static android.server.cts.ActivityManagerState.STATE_RESUMED;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/**
* Build: mmma -j32 cts/hostsidetests/services
* Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerConfigChangeTests
@@ -90,4 +95,56 @@
assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1);
}
}
+
+ /**
+ * Test updating application info when app is running. An activity with matching package name
+ * must be recreated and its asset sequence number must be incremented.
+ */
+ public void testUpdateApplicationInfo() throws Exception {
+ clearLogcat();
+
+ // Launch an activity that prints applied config.
+ launchActivity(TEST_ACTIVITY_NAME);
+ final int assetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME);
+ clearLogcat();
+
+ // Update package info.
+ executeShellCommand("am update-appinfo all " + componentName);
+ mAmWmState.waitForWithAmState(mDevice, (amState) -> {
+ // Wait for activity to be resumed and asset seq number to be updated.
+ try {
+ return readAssetSeqNumber(TEST_ACTIVITY_NAME) == assetSeq + 1
+ && amState.hasActivityState(TEST_ACTIVITY_NAME, STATE_RESUMED);
+ } catch (Exception e) {
+ return false;
+ }
+ }, "Waiting asset sequence number to be updated and for activity to be resumed.");
+
+ // Check if activity is relaunched and asset seq is updated.
+ assertRelaunchOrConfigChanged(TEST_ACTIVITY_NAME, 1 /* numRelaunch */,
+ 0 /* numConfigChange */);
+ final int newAssetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME);
+ assertEquals("Asset sequence number must be incremented.", assetSeq + 1, newAssetSeq);
+ }
+
+ private static final Pattern sConfigurationPattern = Pattern.compile(
+ "(.+): Configuration: \\{(.*) as.(\\d+)(.*)\\}");
+
+ /** Read asset sequence number in last applied configuration from logs. */
+ private int readAssetSeqNumber(String activityName) throws Exception {
+ final String[] lines = getDeviceLogsForComponent(activityName);
+ for (int i = lines.length - 1; i >= 0; i--) {
+ final String line = lines[i].trim();
+ final Matcher matcher = sConfigurationPattern.matcher(line);
+ if (matcher.matches()) {
+ final String assetSeqNumber = matcher.group(3);
+ try {
+ return Integer.valueOf(assetSeqNumber);
+ } catch (NumberFormatException e) {
+ // Ignore, asset seq number is not printed when not set.
+ }
+ }
+ }
+ return 0;
+ }
}
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 51ee527..cb9f01b 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
@@ -32,6 +32,7 @@
import static android.server.cts.ActivityManagerState.STATE_RESUMED;
import static android.server.cts.ActivityManagerState.STATE_STOPPED;
import static android.server.cts.StateLogger.log;
+import static android.server.cts.StateLogger.logE;
/**
* Build: mmma -j32 cts/hostsidetests/services
@@ -39,6 +40,8 @@
*/
public class ActivityManagerDisplayTests extends ActivityManagerTestBase {
private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity processes";
+ private static final String WM_SIZE = "wm size";
+ private static final String WM_DENSITY = "wm density";
private static final String TEST_ACTIVITY_NAME = "TestActivity";
private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
@@ -57,12 +60,40 @@
private boolean mVirtualDisplayCreated;
+ /** Physical display metrics and overrides in the beginning of the test. */
+ private ReportedDisplayMetrics mInitialDisplayMetrics;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mInitialDisplayMetrics = getDisplayMetrics();
+ }
+
@Override
protected void tearDown() throws Exception {
- destroyVirtualDisplays();
+ try {
+ destroyVirtualDisplays();
+ restoreDisplayMetricsOverrides();
+ } catch (DeviceNotAvailableException e) {
+ logE(e.getMessage());
+ }
super.tearDown();
}
+ private void restoreDisplayMetricsOverrides() throws Exception {
+ if (mInitialDisplayMetrics.sizeOverrideSet) {
+ executeShellCommand(WM_SIZE + " " + mInitialDisplayMetrics.overrideWidth + "x"
+ + mInitialDisplayMetrics.overrideHeight);
+ } else {
+ executeShellCommand("wm size reset");
+ }
+ if (mInitialDisplayMetrics.densityOverrideSet) {
+ executeShellCommand(WM_DENSITY + " " + mInitialDisplayMetrics.overrideDensity);
+ } else {
+ executeShellCommand("wm density reset");
+ }
+ }
+
/**
* Tests that the global configuration is equal to the default display's override configuration.
*/
@@ -387,17 +418,10 @@
mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
TEST_ACTIVITY_NAME);
- String displaySize = executeShellCommand("wm size").trim();
- Pattern pattern = Pattern.compile("Physical size: (\\d+)x(\\d+)");
- Matcher matcher = pattern.matcher(displaySize);
- if (matcher.matches()) {
- int width = Integer.parseInt(matcher.group(1));
- int height = Integer.parseInt(matcher.group(2));
-
- executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
- } else {
- throw new RuntimeException("Couldn't find display size \"" + displaySize + "\"");
- }
+ final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
+ final int width = displayMetrics.getWidth();
+ final int height = displayMetrics.getHeight();
+ executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
mAmWmState.computeState(mDevice, new String[] {VIRTUAL_DISPLAY_ACTIVITY});
mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
@@ -477,6 +501,34 @@
externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
}
+ /** Tests that an activity can launch an activity from a different UID into its own task. */
+ public void testPermissionLaunchMultiUidTask() throws Exception {
+ final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
+
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
+ mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
+
+ // Check that the first activity is launched onto the secondary display
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
+ ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityComponentName(LAUNCHING_ACTIVITY),
+ frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+ // Launch an activity from a different UID into the first activity's task
+ getLaunchActivityBuilder()
+ .setTargetPackage(SECOND_PACKAGE_NAME)
+ .setTargetActivityName(SECOND_ACTIVITY_NAME).execute();
+
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+ frontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
+ SECOND_PACKAGE_NAME, SECOND_ACTIVITY_NAME);
+ assertEquals("Secondary display must contain 1 task", 1, frontStack.getTasks().size());
+ }
+
/**
* Test that launching from display owner is allowed even when the the display owner
* doesn't have anything on the display.
@@ -913,6 +965,143 @@
}
}
+ /**
+ * Test that display overrides apply correctly and won't be affected by display changes.
+ * This sets overrides to display size and density, initiates a display changed event by locking
+ * and unlocking the phone and verifies that overrides are kept.
+ * */
+ public void testForceDisplayMetrics() throws Exception {
+ launchHomeActivity();
+
+ // Read initial sizes.
+ final ReportedDisplayMetrics originalDisplayMetrics = getDisplayMetrics();
+
+ // Apply new override values that don't match the physical metrics.
+ final int overrideWidth = (int) (originalDisplayMetrics.physicalWidth * 1.5);
+ final int overrideHeight = (int) (originalDisplayMetrics.physicalHeight * 1.5);
+ executeShellCommand(WM_SIZE + " " + overrideWidth + "x" + overrideHeight);
+ final int overrideDensity = (int) (originalDisplayMetrics.physicalDensity * 1.1);
+ executeShellCommand(WM_DENSITY + " " + overrideDensity);
+
+ // Check if overrides applied correctly.
+ ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
+ assertEquals(overrideWidth, displayMetrics.overrideWidth);
+ assertEquals(overrideHeight, displayMetrics.overrideHeight);
+ assertEquals(overrideDensity, displayMetrics.overrideDensity);
+
+ // Lock and unlock device. This will cause a DISPLAY_CHANGED event to be triggered and
+ // might update the metrics.
+ sleepDevice();
+ wakeUpAndUnlockDevice();
+ mAmWmState.waitForHomeActivityVisible(mDevice);
+
+ // Check if overrides are still applied.
+ displayMetrics = getDisplayMetrics();
+ assertEquals(overrideWidth, displayMetrics.overrideWidth);
+ assertEquals(overrideHeight, displayMetrics.overrideHeight);
+ assertEquals(overrideDensity, displayMetrics.overrideDensity);
+
+ // All overrides will be cleared in tearDown.
+ }
+
+ /** Get physical and override display metrics from WM. */
+ private ReportedDisplayMetrics getDisplayMetrics() throws Exception {
+ mDumpLines.clear();
+ final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
+ mDevice.executeShellCommand(WM_SIZE, outputReceiver);
+ mDevice.executeShellCommand(WM_DENSITY, outputReceiver);
+ final String dump = outputReceiver.getOutput();
+ mDumpLines.clear();
+ Collections.addAll(mDumpLines, dump.split("\\n"));
+ return ReportedDisplayMetrics.create(mDumpLines);
+ }
+
+ private static class ReportedDisplayMetrics {
+ private static final Pattern sPhysicalSizePattern =
+ Pattern.compile("Physical size: (\\d+)x(\\d+)");
+ private static final Pattern sOverrideSizePattern =
+ Pattern.compile("Override size: (\\d+)x(\\d+)");
+ private static final Pattern sPhysicalDensityPattern =
+ Pattern.compile("Physical density: (\\d+)");
+ private static final Pattern sOverrideDensityPattern =
+ Pattern.compile("Override density: (\\d+)");
+
+ int physicalWidth;
+ int physicalHeight;
+ int physicalDensity;
+
+ boolean sizeOverrideSet;
+ int overrideWidth;
+ int overrideHeight;
+ boolean densityOverrideSet;
+ int overrideDensity;
+
+ /** Get width that WM operates with. */
+ int getWidth() {
+ return sizeOverrideSet ? overrideWidth : physicalWidth;
+ }
+
+ /** Get height that WM operates with. */
+ int getHeight() {
+ return sizeOverrideSet ? overrideHeight : physicalHeight;
+ }
+
+ /** Get density that WM operates with. */
+ int getDensity() {
+ return densityOverrideSet ? overrideDensity : physicalDensity;
+ }
+
+ static ReportedDisplayMetrics create(LinkedList<String> dump) {
+ final ReportedDisplayMetrics result = new ReportedDisplayMetrics();
+
+ boolean physicalSizeFound = false;
+ boolean physicalDensityFound = false;
+
+ while (!dump.isEmpty()) {
+ final String line = dump.pop().trim();
+
+ Matcher matcher = sPhysicalSizePattern.matcher(line);
+ if (matcher.matches()) {
+ physicalSizeFound = true;
+ log(line);
+ result.physicalWidth = Integer.parseInt(matcher.group(1));
+ result.physicalHeight = Integer.parseInt(matcher.group(2));
+ continue;
+ }
+
+ matcher = sOverrideSizePattern.matcher(line);
+ if (matcher.matches()) {
+ log(line);
+ result.overrideWidth = Integer.parseInt(matcher.group(1));
+ result.overrideHeight = Integer.parseInt(matcher.group(2));
+ result.sizeOverrideSet = true;
+ continue;
+ }
+
+ matcher = sPhysicalDensityPattern.matcher(line);
+ if (matcher.matches()) {
+ physicalDensityFound = true;
+ log(line);
+ result.physicalDensity = Integer.parseInt(matcher.group(1));
+ continue;
+ }
+
+ matcher = sOverrideDensityPattern.matcher(line);
+ if (matcher.matches()) {
+ log(line);
+ result.overrideDensity = Integer.parseInt(matcher.group(1));
+ result.densityOverrideSet = true;
+ continue;
+ }
+ }
+
+ assertTrue("Physical display size must be reported", physicalSizeFound);
+ assertTrue("Physical display density must be reported", physicalDensityFound);
+
+ return result;
+ }
+ }
+
/** Assert that component received onMovedToDisplay and onConfigurationChanged callbacks. */
private void assertMovedToDisplay(String componentName) throws Exception {
final ActivityLifecycleCounts lifecycleCounts
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
index 01d98a4..de8194a 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
@@ -244,14 +244,10 @@
// Each time we compute the state we implicitly assert valid bounds.
String[] waitForActivitiesVisible =
new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
- setDeviceRotation(0);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(1);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(2);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(3);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ for (int i = 0; i < 4; i++) {
+ setDeviceRotation(i);
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ }
// Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for double
// step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
setDeviceRotation(1);
@@ -278,29 +274,46 @@
String[] waitForActivitiesVisible =
new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
- sleepDevice();
- setDeviceRotation(0);
- wakeUpAndUnlockDevice();
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ for (int i = 0; i < 4; i++) {
+ sleepDevice();
+ setDeviceRotation(i);
+ wakeUpAndUnlockDevice();
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ }
+ }
- sleepDevice();
+ public void testRotationWhileDockMinimized() throws Exception {
+ launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
+ assertDockMinimized();
+ mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
+ mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
+ mAmWmState.assertFocusedStack("Home activity should be focused in minimized mode",
+ HOME_STACK_ID);
+
+ // Rotate device single steps (90°) 0-1-2-3.
+ // Each time we compute the state we implicitly assert valid bounds in minimized mode.
+ String[] waitForActivitiesVisible = new String[] {TEST_ACTIVITY_NAME};
+ for (int i = 0; i < 4; i++) {
+ setDeviceRotation(i);
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ }
+
+ // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for double
+ // step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side in minimized
+ // mode.
setDeviceRotation(1);
- wakeUpAndUnlockDevice();
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-
- sleepDevice();
- setDeviceRotation(2);
- wakeUpAndUnlockDevice();
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-
- sleepDevice();
setDeviceRotation(3);
- wakeUpAndUnlockDevice();
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ setDeviceRotation(0);
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ setDeviceRotation(2);
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ setDeviceRotation(0);
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
}
public void testFinishDockActivityWhileMinimized() throws Exception {
- assertDockNotMinimized();
launchActivityInDockStackAndMinimize(FINISHABLE_ACTIVITY_NAME);
assertDockMinimized();
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 7a5c02e..a3a436f 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
@@ -597,12 +597,12 @@
public void testAppOpsDenyPipOnPause() throws Exception {
if (!supportsPip()) return;
- // Disable enter-pip-on-hide and try to enter pip
+ // Disable enter-pip and try to enter pip
setAppOpsOpToMode(ActivityManagerTestBase.componentName,
APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE, APP_OPS_MODE_IGNORED);
// Launch the PIP activity on pause
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
assertPinnedStackDoesNotExist();
// Go home and ensure that there is no pinned stack
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java
index ca86c9b..ece45f2 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java
@@ -16,7 +16,10 @@
package android.server.cts;
+import static android.server.cts.ActivityManagerState.RESIZE_MODE_RESIZEABLE;
+import static android.server.cts.ActivityManagerTestBase.DOCKED_STACK_ID;
import static android.server.cts.ActivityManagerTestBase.FREEFORM_WORKSPACE_STACK_ID;
+import static android.server.cts.ActivityManagerTestBase.HOME_STACK_ID;
import static android.server.cts.ActivityManagerTestBase.PINNED_STACK_ID;
import static android.server.cts.ActivityManagerTestBase.componentName;
import static android.server.cts.StateLogger.log;
@@ -587,6 +590,11 @@
}
void assertValidBounds(boolean compareTaskAndStackBounds) {
+ // Cycle through the stacks and tasks to figure out if the home stack is resizable
+ final ActivityTask homeTask = mAmState.getHomeTask();
+ final boolean homeStackIsResizable = homeTask != null
+ && homeTask.getResizeMode().equals(RESIZE_MODE_RESIZEABLE);
+
for (ActivityStack aStack : mAmState.getStacks()) {
final int stackId = aStack.mStackId;
final WindowStack wStack = mWmState.getStack(stackId);
@@ -618,10 +626,36 @@
final Rectangle aTaskBounds = aTask.getBounds();
final Rectangle wTaskBounds = wTask.getBounds();
+ final Rectangle displayRect = mWmState.getDisplay(aStack.mDisplayId)
+ .getDisplayRect();
if (aTaskIsFullscreen) {
assertNull("Task bounds in AM must be null for fullscreen taskId=" + taskId,
aTaskBounds);
+ } else if (!homeStackIsResizable && mWmState.isDockedStackMinimized()
+ && displayRect.getWidth() > displayRect.getHeight()) {
+ // When minimized using non-resizable launcher in landscape mode, it will move
+ // the task offscreen in the negative x direction unlike portrait that crops.
+ // The x value in the task bounds will not match the stack bounds since the
+ // only the task was moved.
+ assertEquals("Task bounds in AM and WM must match width taskId=" + taskId
+ + ", stackId" + stackId, aTaskBounds.getWidth(),
+ wTaskBounds.getWidth());
+ assertEquals("Task bounds in AM and WM must match height taskId=" + taskId
+ + ", stackId" + stackId, aTaskBounds.getHeight(),
+ wTaskBounds.getHeight());
+ assertEquals("Task bounds must match stack bounds y taskId=" + taskId
+ + ", stackId" + stackId, aTaskBounds.getY(),
+ wTaskBounds.getY());
+ assertEquals("Task and stack bounds must match width taskId=" + taskId
+ + ", stackId" + stackId, aStackBounds.getWidth(),
+ wTaskBounds.getWidth());
+ assertEquals("Task and stack bounds must match height taskId=" + taskId
+ + ", stackId" + stackId, aStackBounds.getHeight(),
+ wTaskBounds.getHeight());
+ assertEquals("Task and stack bounds must match y taskId=" + taskId
+ + ", stackId" + stackId, aStackBounds.getY(),
+ wTaskBounds.getY());
} else {
assertEquals("Task bounds in AM and WM must be equal taskId=" + taskId
+ ", stackId=" + stackId, aTaskBounds, wTaskBounds);
@@ -651,6 +685,30 @@
// bounds must be equal.
assertEquals("Task bounds must be equal to stack bounds taskId="
+ taskId + ", stackId=" + stackId, aStackBounds, wTaskBounds);
+ } else if (stackId == DOCKED_STACK_ID && homeStackIsResizable
+ && mWmState.isDockedStackMinimized()) {
+ // Portrait if the display height is larger than the width
+ if (displayRect.getHeight() > displayRect.getWidth()) {
+ assertEquals("Task width must be equal to stack width taskId="
+ + taskId + ", stackId=" + stackId,
+ aStackBounds.getWidth(), wTaskBounds.getWidth());
+ assertTrue("Task height must be greater than stack height "
+ + "taskId=" + taskId + ", stackId=" + stackId,
+ aStackBounds.getHeight() < wTaskBounds.getHeight());
+ assertEquals("Task and stack x position must be equal taskId="
+ + taskId + ", stackId=" + stackId,
+ wTaskBounds.getX(), wStackBounds.getX());
+ } else {
+ assertTrue("Task width must be greater than stack width taskId="
+ + taskId + ", stackId=" + stackId,
+ aStackBounds.getWidth() < wTaskBounds.getWidth());
+ assertEquals("Task height must be equal to stack height taskId="
+ + taskId + ", stackId=" + stackId,
+ aStackBounds.getHeight(), wTaskBounds.getHeight());
+ assertEquals("Task and stack y position must be equal taskId="
+ + taskId + ", stackId=" + stackId, wTaskBounds.getY(),
+ wStackBounds.getY());
+ }
} else {
// Minimal dimensions affect task size, so bounds of task and stack must
// be different - will compare dimensions instead.
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 ae42173..6565215 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java
@@ -44,6 +44,8 @@
public static final String STATE_PAUSED = "PAUSED";
public static final String STATE_STOPPED = "STOPPED";
+ public static final String RESIZE_MODE_RESIZEABLE = "RESIZE_MODE_RESIZEABLE";
+
private static final String DUMPSYS_ACTIVITY_ACTIVITIES = "dumpsys activity activities";
// Copied from ActivityRecord.java
@@ -315,24 +317,24 @@
return activity.name;
}
- private Activity getHomeActivity() {
- for (ActivityStack stack : mStacks) {
- if (stack.mStackId != HOME_STACK_ID) {
- continue;
- }
-
- for (ActivityTask task : stack.mTasks) {
- if (task.mTaskType != HOME_ACTIVITY_TYPE) {
- continue;
+ ActivityTask getHomeTask() {
+ ActivityStack homeStack = getStackById(HOME_STACK_ID);
+ if (homeStack != null) {
+ for (ActivityTask task : homeStack.mTasks) {
+ if (task.mTaskType == HOME_ACTIVITY_TYPE) {
+ return task;
}
- return task.mActivities.get(task.mActivities.size() - 1);
}
-
return null;
}
return null;
}
+ private Activity getHomeActivity() {
+ final ActivityTask homeTask = getHomeTask();
+ return homeTask != null ? homeTask.mActivities.get(homeTask.mActivities.size() - 1) : null;
+ }
+
ActivityTask getTaskByActivityName(String activityName) {
return getTaskByActivityName(activityName, -1);
}
@@ -482,6 +484,9 @@
+ "isPersistable=(\\S+) numFullscreen=(\\d+) taskType=(\\d+) "
+ "mTaskToReturnTo=(\\d+)");
+ private static final Pattern RESIZABLE_PATTERN = Pattern.compile(
+ ".*mResizeMode=([^\\s]+).*");
+
int mTaskId;
int mStackId;
Rectangle mLastNonFullscreenBounds;
@@ -490,6 +495,7 @@
ArrayList<Activity> mActivities = new ArrayList();
int mTaskType = -1;
int mReturnToType = -1;
+ private String mResizeMode;
private ActivityTask() {
}
@@ -587,8 +593,20 @@
mReturnToType = Integer.valueOf(matcher.group(5));
continue;
}
+
+ matcher = RESIZABLE_PATTERN.matcher(line);
+ if (matcher.matches()) {
+ log(line);
+ mResizeMode = matcher.group(1);
+ log(mResizeMode);
+ continue;
+ }
}
}
+
+ public String getResizeMode() {
+ return mResizeMode;
+ }
}
static class Activity {
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
index f4a09b0..71139d6 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
@@ -104,6 +104,10 @@
static final String FINISH_ACTIVITY_BROADCAST
= "am broadcast -a trigger_broadcast --ez finish true";
+ /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
+ static final String MOVE_TASK_TO_BACK_BROADCAST
+ = "am broadcast -a trigger_broadcast --ez moveToBack true";
+
private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
private static final String AM_RESIZE_STACK = "am stack resize ";
@@ -146,12 +150,20 @@
protected static String getAmStartCmd(final String activityName, final int displayId) {
return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000"
+ " --display " + displayId;
- }
+ }
+
+ protected static String getAmStartCmdInNewTask(final String activityName) {
+ return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000";
+ }
protected static String getAmStartCmdOverHome(final String activityName) {
return "am start --activity-task-on-home -n " + getActivityComponentName(activityName);
}
+ protected static String getOrientationBroadcast(int orientation) {
+ return "am broadcast -a trigger_broadcast --ei orientation " + orientation;
+ }
+
static String getActivityComponentName(final String activityName) {
return getActivityComponentName(componentName, activityName);
}
@@ -276,6 +288,11 @@
mAmWmState.waitForValidState(mDevice, targetActivityName);
}
+ protected void launchActivityInNewTask(final String targetActivityName) throws Exception {
+ executeShellCommand(getAmStartCmdInNewTask(targetActivityName));
+ mAmWmState.waitForValidState(mDevice, targetActivityName);
+ }
+
/**
* Starts an activity in a new stack.
* @return the stack id of the newly created stack.
@@ -843,6 +860,7 @@
private final ITestDevice mDevice;
private String mTargetActivityName;
+ private String mTargetPackage = componentName;
private boolean mToSide;
private boolean mRandomData;
private boolean mMultipleTask;
@@ -881,6 +899,11 @@
return this;
}
+ public LaunchActivityBuilder setTargetPackage(String pkg) {
+ mTargetPackage = pkg;
+ return this;
+ }
+
public LaunchActivityBuilder setDisplayId(int id) {
mDisplayId = id;
return this;
@@ -912,13 +935,15 @@
}
if (mTargetActivityName != null) {
commandBuilder.append(" --es target_activity ").append(mTargetActivityName);
+ commandBuilder.append(" --es package_name ").append(mTargetPackage);
}
if (mDisplayId != INVALID_DISPLAY_ID) {
commandBuilder.append(" --ei display_id ").append(mDisplayId);
}
executeShellCommand(mDevice, commandBuilder.toString());
- mAmWmState.waitForValidState(mDevice, mTargetActivityName);
+ mAmWmState.waitForValidState(mDevice, new String[]{mTargetActivityName},
+ null /* stackIds */, false /* compareTaskAndStackBounds */, mTargetPackage);
}
}
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/TriggerContentJobService.java b/tests/JobScheduler/src/android/jobscheduler/TriggerContentJobService.java
index d87f1a7..d4ad1c8 100644
--- a/tests/JobScheduler/src/android/jobscheduler/TriggerContentJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/TriggerContentJobService.java
@@ -47,12 +47,17 @@
JobParameters mRunningParams;
final Handler mHandler = new Handler();
- final Runnable mWorker = new Runnable() {
+ final Runnable mWorkerReschedule = new Runnable() {
@Override public void run() {
scheduleJob(TriggerContentJobService.this, mRunningJobInfo);
jobFinished(mRunningParams, false);
}
};
+ final Runnable mWorkerFinishTrue = new Runnable() {
+ @Override public void run() {
+ jobFinished(mRunningParams, true);
+ }
+ };
public static void scheduleJob(Context context, JobInfo jobInfo) {
JobScheduler js = context.getSystemService(JobScheduler.class);
@@ -74,9 +79,13 @@
TestEnvironment.getTestEnvironment().setMode(TestEnvironment.MODE_ONESHOT, null);
TestEnvironment.getTestEnvironment().notifyExecution(params);
- if (mode == TestEnvironment.MODE_ONE_REPEAT) {
+ if (mode == TestEnvironment.MODE_ONE_REPEAT_RESCHEDULE) {
mRunningParams = params;
- mHandler.postDelayed(mWorker, REPEAT_INTERVAL);
+ mHandler.postDelayed(mWorkerReschedule, REPEAT_INTERVAL);
+ return true;
+ } else if (mode == TestEnvironment.MODE_ONE_REPEAT_FINISH_TRUE) {
+ mRunningParams = params;
+ mHandler.postDelayed(mWorkerFinishTrue, REPEAT_INTERVAL);
return true;
} else {
return false; // No work to do.
@@ -104,7 +113,8 @@
private JobInfo mModeJobInfo;
public static final int MODE_ONESHOT = 0;
- public static final int MODE_ONE_REPEAT = 1;
+ public static final int MODE_ONE_REPEAT_RESCHEDULE = 1;
+ public static final int MODE_ONE_REPEAT_FINISH_TRUE = 2;
public static TestEnvironment getTestEnvironment() {
if (kTestEnvironment == null) {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
index ef1ca67..42637d0 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
@@ -151,6 +151,12 @@
assertFalse("Job with charging constraint fired while not on power.",
kTestEnvironment.awaitExecution());
+
+ // And for good measure, ensure the job runs once the device is plugged in.
+ kTestEnvironment.setExpectedExecutions(1);
+ setBatteryState(true, 100);
+ assertTrue("Job with charging constraint did not fire on power.",
+ kTestEnvironment.awaitExecution());
}
/**
@@ -165,5 +171,11 @@
assertFalse("Job with battery not low constraint fired while level critical.",
kTestEnvironment.awaitExecution());
+
+ // And for good measure, ensure the job runs once the device's battery level is not low.
+ kTestEnvironment.setExpectedExecutions(1);
+ setBatteryState(false, 50);
+ assertTrue("Job with not low constraint did not fire when charge increased.",
+ kTestEnvironment.awaitExecution());
}
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java
new file mode 100644
index 0000000..c72af8f
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.jobscheduler.cts;
+
+
+import android.annotation.TargetApi;
+import android.app.job.JobInfo;
+import android.os.SystemClock;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+/**
+ * Schedules jobs with the {@link android.app.job.JobScheduler} that have battery constraints.
+ */
+@TargetApi(26)
+public class StorageConstraintTest extends ConstraintTest {
+ private static final String TAG = "StorageConstraintTest";
+
+ /** Unique identifier for the job scheduled by this suite of tests. */
+ public static final int STORAGE_JOB_ID = StorageConstraintTest.class.hashCode();
+
+ private JobInfo.Builder mBuilder;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mBuilder = new JobInfo.Builder(STORAGE_JOB_ID, kJobServiceComponent);
+ String res = SystemUtil.runShellCommand(getInstrumentation(), "cmd activity set-inactive "
+ + mContext.getPackageName() + " false");
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mJobScheduler.cancel(STORAGE_JOB_ID);
+ // Put storage service back in to normal operation.
+ SystemUtil.runShellCommand(getInstrumentation(), "cmd devicestoragemonitor reset");
+ }
+
+ void setStorageState(boolean low) throws Exception {
+ String res;
+ if (low) {
+ res = SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd devicestoragemonitor force-low -f");
+ } else {
+ res = SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd devicestoragemonitor force-not-low -f");
+ }
+ int seq = Integer.parseInt(res.trim());
+ long startTime = SystemClock.elapsedRealtime();
+
+ // Wait for the battery update to be processed by job scheduler before proceeding.
+ int curSeq;
+ do {
+ curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd jobscheduler get-storage-seq").trim());
+ if (curSeq == seq) {
+ return;
+ }
+ } while ((SystemClock.elapsedRealtime()-startTime) < 1000);
+
+ fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq);
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // Positives - schedule jobs under conditions that require them to pass.
+ // --------------------------------------------------------------------------------------------
+
+ /**
+ * Schedule a job that requires the device storage is not low, when it is actually not low.
+ */
+ public void testNotLowConstraintExecutes() throws Exception {
+ setStorageState(false);
+
+ kTestEnvironment.setExpectedExecutions(1);
+ mJobScheduler.schedule(mBuilder.setRequiresStorageNotLow(true).build());
+
+ assertTrue("Job with storage not low constraint did not fire when storage not low.",
+ kTestEnvironment.awaitExecution());
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // Negatives - schedule jobs under conditions that require that they fail.
+ // --------------------------------------------------------------------------------------------
+
+ /**
+ * Schedule a job that requires the device storage is not low, when it actually is low.
+ */
+ public void testNotLowConstraintFails() throws Exception {
+ setStorageState(true);
+
+ kTestEnvironment.setExpectedExecutions(0);
+ mJobScheduler.schedule(mBuilder.setRequiresStorageNotLow(true).build());
+
+ assertFalse("Job with storage now low constraint fired while low.",
+ kTestEnvironment.awaitExecution());
+
+ // And for good measure, ensure the job runs once storage is okay.
+ kTestEnvironment.setExpectedExecutions(1);
+ setStorageState(false);
+ assertTrue("Job with storage not low constraint did not fire when storage not low.",
+ kTestEnvironment.awaitExecution());
+ }
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TriggerContentTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/TriggerContentTest.java
index 32959a0..8b71171 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TriggerContentTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TriggerContentTest.java
@@ -243,8 +243,8 @@
JobInfo triggerJob = makeJobInfo(uribase,
JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
kTriggerTestEnvironment.setExpectedExecutions(1);
- kTriggerTestEnvironment.setMode(TriggerContentJobService.TestEnvironment.MODE_ONE_REPEAT,
- triggerJob);
+ kTriggerTestEnvironment.setMode(
+ TriggerContentJobService.TestEnvironment.MODE_ONE_REPEAT_RESCHEDULE, triggerJob);
mJobScheduler.schedule(triggerJob);
// Report changes.
@@ -290,8 +290,8 @@
// Start watching.
JobInfo triggerJob = makeJobInfo(uribase, 0);
kTriggerTestEnvironment.setExpectedExecutions(1);
- kTriggerTestEnvironment.setMode(TriggerContentJobService.TestEnvironment.MODE_ONE_REPEAT,
- triggerJob);
+ kTriggerTestEnvironment.setMode(
+ TriggerContentJobService.TestEnvironment.MODE_ONE_REPEAT_RESCHEDULE, triggerJob);
mJobScheduler.schedule(triggerJob);
// Report changes.
@@ -327,14 +327,80 @@
assertEquals(DummyJobContentProvider.AUTHORITY, auths[0]);
}
- public void testPhotoAdded() throws Exception {
+ public void testPhotoAdded_Reschedule() throws Exception {
JobInfo triggerJob = makePhotosJobInfo();
kTriggerTestEnvironment.setExpectedExecutions(1);
- kTriggerTestEnvironment.setMode(TriggerContentJobService.TestEnvironment.MODE_ONE_REPEAT,
+ kTriggerTestEnvironment.setMode(
+ TriggerContentJobService.TestEnvironment.MODE_ONE_REPEAT_RESCHEDULE, triggerJob);
+ mJobScheduler.schedule(triggerJob);
+
+ // Create a file that our job should see.
+ makeActiveFile(0, new File(DCIM_DIR, PIC_1_NAME),
+ getContext().getResources().getAssets().open("violet.jpg"));
+ assertNotNull(mActiveUris[0]);
+
+ // Wait for the job to wake up with the change and verify it.
+ boolean executed = kTriggerTestEnvironment.awaitExecution();
+ kTriggerTestEnvironment.setExpectedExecutions(1);
+ assertTrue("Timed out waiting for trigger content.", executed);
+ JobParameters params = kTriggerTestEnvironment.getLastJobParameters();
+ Uri[] uris = params.getTriggeredContentUris();
+ assertUriArrayLength(1, uris);
+ assertEquals(mActiveUris[0], uris[0]);
+ String[] auths = params.getTriggeredContentAuthorities();
+ assertEquals(1, auths.length);
+ assertEquals(MediaStore.AUTHORITY, auths[0]);
+
+ // While the job is still running, create another file it should see.
+ // (This tests that it will see changes that happen before the next job
+ // is scheduled.)
+ makeActiveFile(1, new File(DCIM_DIR, PIC_2_NAME),
+ getContext().getResources().getAssets().open("violet.jpg"));
+ assertNotNull(mActiveUris[1]);
+
+ // Wait for the job to wake up and verify it saw the change.
+ executed = kTriggerTestEnvironment.awaitExecution();
+ assertTrue("Timed out waiting for trigger content.", executed);
+ params = kTriggerTestEnvironment.getLastJobParameters();
+ uris = params.getTriggeredContentUris();
+ assertUriArrayLength(1, uris);
+ assertEquals(mActiveUris[1], uris[0]);
+ auths = params.getTriggeredContentAuthorities();
+ assertEquals(1, auths.length);
+ assertEquals(MediaStore.AUTHORITY, auths[0]);
+
+ // Schedule a new job to look at what we see when deleting the files.
+ kTriggerTestEnvironment.setExpectedExecutions(1);
+ kTriggerTestEnvironment.setMode(TriggerContentJobService.TestEnvironment.MODE_ONESHOT,
triggerJob);
mJobScheduler.schedule(triggerJob);
+ // Delete the files. Note that this will result in a general change, not for specific URIs.
+ cleanupActive(0);
+ cleanupActive(1);
+
+ // Wait for the job to wake up and verify it saw the change.
+ executed = kTriggerTestEnvironment.awaitExecution();
+ assertTrue("Timed out waiting for trigger content.", executed);
+ params = kTriggerTestEnvironment.getLastJobParameters();
+ uris = params.getTriggeredContentUris();
+ assertUriArrayLength(1, uris);
+ assertEquals(MEDIA_EXTERNAL_URI, uris[0]);
+ auths = params.getTriggeredContentAuthorities();
+ assertEquals(1, auths.length);
+ assertEquals(MediaStore.AUTHORITY, auths[0]);
+ }
+
+ // Doesn't work. Should it?
+ public void xxxtestPhotoAdded_FinishTrue() throws Exception {
+ JobInfo triggerJob = makePhotosJobInfo();
+
+ kTriggerTestEnvironment.setExpectedExecutions(1);
+ kTriggerTestEnvironment.setMode(
+ TriggerContentJobService.TestEnvironment.MODE_ONE_REPEAT_FINISH_TRUE, triggerJob);
+ mJobScheduler.schedule(triggerJob);
+
// Create a file that our job should see.
makeActiveFile(0, new File(DCIM_DIR, PIC_1_NAME),
getContext().getResources().getAssets().open("violet.jpg"));
diff --git a/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java b/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java
index e3b7c4b..f20243b 100644
--- a/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java
+++ b/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java
@@ -177,7 +177,7 @@
.onActivityResult(
Mockito.eq(REQUEST_CODE_ACTIVATE_ADMIN),
Mockito.anyInt(),
- Mockito.any(Intent.class));
+ Mockito.nullable(Intent.class));
}
private void assertWithTimeoutOnActivityResultInvokedWithResultCode(int expectedResultCode) {
@@ -186,7 +186,7 @@
.onActivityResult(
Mockito.eq(REQUEST_CODE_ACTIVATE_ADMIN),
resultCodeCaptor.capture(),
- Mockito.any(Intent.class));
+ Mockito.nullable(Intent.class));
assertEquals(expectedResultCode, (int) resultCodeCaptor.getValue());
}
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index a5ea701..5def13b 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -331,6 +331,9 @@
<activity android:name="android.app.stubs.DisplayTestActivity"
android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout" />
+ <activity android:name="android.app.stubs.ToolbarActivity"
+ android:theme="@android:style/Theme.Material.Light.NoActionBar" />
+
<service
android:name="android.app.stubs.LiveWallpaper"
android:icon="@drawable/robot"
diff --git a/tests/app/app/res/layout/toolbar_activity.xml b/tests/app/app/res/layout/toolbar_activity.xml
new file mode 100644
index 0000000..cf1f117
--- /dev/null
+++ b/tests/app/app/res/layout/toolbar_activity.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+<Toolbar android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/actionBarSize" />
+
+</LinearLayout>
+
diff --git a/tests/app/app/res/menu/flat_menu.xml b/tests/app/app/res/menu/flat_menu.xml
new file mode 100644
index 0000000..a71ff57
--- /dev/null
+++ b/tests/app/app/res/menu/flat_menu.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/item_1"
+ android:title="@string/relative_view1"/>
+ <item android:id="@+id/item_2"
+ android:title="@string/relative_view2"/>
+</menu>
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/ActionBarActivity.java b/tests/app/app/src/android/app/stubs/ActionBarActivity.java
index ec27b1b..5e15407 100644
--- a/tests/app/app/src/android/app/stubs/ActionBarActivity.java
+++ b/tests/app/app/src/android/app/stubs/ActionBarActivity.java
@@ -18,6 +18,7 @@
import android.app.ActionBar;
import android.app.Activity;
import android.os.Bundle;
+import android.view.Menu;
public class ActionBarActivity extends Activity {
@@ -29,4 +30,10 @@
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}
}
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.flat_menu, menu);
+ return true;
+ }
}
diff --git a/tests/app/app/src/android/app/stubs/ToolbarActivity.java b/tests/app/app/src/android/app/stubs/ToolbarActivity.java
new file mode 100644
index 0000000..3dbf2fe
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/ToolbarActivity.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 android.app.stubs;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Toolbar;
+
+public class ToolbarActivity extends Activity {
+ private Toolbar mToolbar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.toolbar_activity);
+ mToolbar = (Toolbar) findViewById(R.id.toolbar);
+ setActionBar(mToolbar);
+ }
+
+ public Toolbar getToolbar() {
+ return mToolbar;
+ }
+}
diff --git a/tests/app/src/android/app/cts/ActionBarTest.java b/tests/app/src/android/app/cts/ActionBarTest.java
index 3404b57..e6da6cb 100644
--- a/tests/app/src/android/app/cts/ActionBarTest.java
+++ b/tests/app/src/android/app/cts/ActionBarTest.java
@@ -22,6 +22,8 @@
import android.app.stubs.ActionBarActivity;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
+import android.view.KeyEvent;
+import android.view.Window;
public class ActionBarTest extends ActivityInstrumentationTestCase2<ActionBarActivity> {
@@ -81,6 +83,33 @@
assertEquals(t3, mBar.getTabAt(4));
}
+ public void testOptionsMenuKey() {
+ final boolean menuIsVisible[] = {false};
+ mActivity.getActionBar().addOnMenuVisibilityListener(
+ isVisible -> menuIsVisible[0] = isVisible);
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(menuIsVisible[0]);
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+ getInstrumentation().waitForIdleSync();
+ assertFalse(menuIsVisible[0]);
+ }
+
+ public void testOpenOptionsMenu() {
+ if (!mActivity.getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
+ return;
+ }
+ final boolean menuIsVisible[] = {false};
+ mActivity.getActionBar().addOnMenuVisibilityListener(
+ isVisible -> menuIsVisible[0] = isVisible);
+ getInstrumentation().runOnMainSync(() -> mActivity.openOptionsMenu());
+ getInstrumentation().waitForIdleSync();
+ assertTrue(menuIsVisible[0]);
+ getInstrumentation().runOnMainSync(() -> mActivity.closeOptionsMenu());
+ getInstrumentation().waitForIdleSync();
+ assertFalse(menuIsVisible[0]);
+ }
+
private Tab createTab(String name) {
return mBar.newTab().setText("Tab 1").setTabListener(new TestTabListener());
}
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index a2df4bd..74047be 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -16,6 +16,8 @@
package android.app.cts;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -35,20 +37,21 @@
public void testDescribeContents() {
final int expected = 0;
NotificationChannel channel =
- new NotificationChannel("1", "1", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "1", IMPORTANCE_DEFAULT);
assertEquals(expected, channel.describeContents());
}
public void testConstructor() {
NotificationChannel channel =
- new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
assertEquals("1", channel.getId());
assertEquals("one", channel.getName());
+ assertEquals(null, channel.getDescription());
assertEquals(false, channel.canBypassDnd());
assertEquals(false, channel.shouldShowLights());
assertEquals(false, channel.shouldVibrate());
assertEquals(null, channel.getVibrationPattern());
- assertEquals(NotificationManager.IMPORTANCE_DEFAULT, channel.getImportance());
+ assertEquals(IMPORTANCE_DEFAULT, channel.getImportance());
assertEquals(null, channel.getSound());
assertTrue(channel.canShowBadge());
assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, channel.getAudioAttributes());
@@ -58,7 +61,7 @@
public void testWriteToParcel() {
NotificationChannel channel =
- new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
Parcel parcel = Parcel.obtain();
channel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -66,9 +69,21 @@
assertEquals(channel, channel1);
}
+ public void testName() {
+ NotificationChannel channel = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
+ channel.setName("new name");
+ assertEquals("new name", channel.getName());
+ }
+
+ public void testDescription() {
+ NotificationChannel channel = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
+ channel.setDescription("success");
+ assertEquals("success", channel.getDescription());
+ }
+
public void testLights() {
NotificationChannel channel =
- new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
channel.enableLights(true);
assertTrue(channel.shouldShowLights());
channel.enableLights(false);
@@ -77,7 +92,7 @@
public void testLightColor() {
NotificationChannel channel =
- new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
channel.setLightColor(Color.RED);
assertFalse(channel.shouldShowLights());
assertEquals(Color.RED, channel.getLightColor());
@@ -85,7 +100,7 @@
public void testVibration() {
NotificationChannel channel =
- new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
channel.enableVibration(true);
assertTrue(channel.shouldVibrate());
channel.enableVibration(false);
@@ -95,7 +110,7 @@
public void testVibrationPattern() {
final long[] pattern = new long[] {1, 7, 1, 7, 3};
NotificationChannel channel =
- new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
assertNull(channel.getVibrationPattern());
channel.setVibrationPattern(pattern);
assertEquals(pattern, channel.getVibrationPattern());
@@ -117,7 +132,7 @@
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
.build();
NotificationChannel channel =
- new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
channel.setSound(expected, attributes);
assertEquals(expected, channel.getSound());
assertEquals(attributes, channel.getAudioAttributes());
@@ -125,14 +140,14 @@
public void testShowBadge() {
NotificationChannel channel =
- new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
channel.setShowBadge(true);
assertTrue(channel.canShowBadge());
}
public void testGroup() {
NotificationChannel channel =
- new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
channel.setGroup("banana");
assertEquals("banana", channel.getGroup());
}
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 17b9ba9..521d5a3 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -109,6 +109,7 @@
public void testCreateChannel() throws Exception {
final NotificationChannel channel =
new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription("bananas");
channel.enableVibration(true);
channel.setVibrationPattern(new long[] {5, 8, 2, 1});
channel.setSound(new Uri.Builder().scheme("test").build(),
@@ -433,6 +434,7 @@
}
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getDescription(), actual.getDescription());
assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
assertEquals(expected.getImportance(), actual.getImportance());
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index c0877bc..d164b37 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -74,7 +74,7 @@
public void testBuilderConstructor() {
mNotification = new Notification.Builder(mContext, CHANNEL.getId()).build();
assertEquals(CHANNEL.getId(), mNotification.getChannel());
- assertEquals(Notification.BADGE_ICON_LARGE, mNotification.getBadgeIcon());
+ assertEquals(Notification.BADGE_ICON_NONE, mNotification.getBadgeIconType());
assertNull(mNotification.getShortcutId());
assertEquals((long) 0, mNotification.getTimeout());
}
@@ -88,7 +88,7 @@
public void testWriteToParcel() {
mNotification = new Notification.Builder(mContext, CHANNEL.getId())
- .chooseBadgeIcon(Notification.BADGE_ICON_SMALL)
+ .chooseBadgeIconType(Notification.BADGE_ICON_SMALL)
.setShortcutId(SHORTCUT_ID)
.setTimeout(TIMEOUT)
.build();
@@ -142,7 +142,7 @@
assertEquals(mNotification.ledOffMS, result.ledOffMS);
assertEquals(mNotification.iconLevel, result.iconLevel);
assertEquals(mNotification.getShortcutId(), result.getShortcutId());
- assertEquals(mNotification.getBadgeIcon(), result.getBadgeIcon());
+ assertEquals(mNotification.getBadgeIconType(), result.getBadgeIconType());
assertEquals(mNotification.getTimeout(), result.getTimeout());
assertEquals(mNotification.getChannel(), result.getChannel());
@@ -200,7 +200,7 @@
.setContentTitle(CONTENT_TITLE)
.setContentText(CONTENT_TEXT)
.setContentIntent(contentIntent)
- .chooseBadgeIcon(Notification.BADGE_ICON_NONE)
+ .chooseBadgeIconType(Notification.BADGE_ICON_SMALL)
.setShortcutId(SHORTCUT_ID)
.setTimeout(TIMEOUT)
.build();
@@ -209,7 +209,7 @@
assertEquals(1, mNotification.icon);
assertEquals(contentIntent, mNotification.contentIntent);
assertEquals(CHANNEL.getId(), mNotification.getChannel());
- assertEquals(Notification.BADGE_ICON_NONE, mNotification.getBadgeIcon());
+ assertEquals(Notification.BADGE_ICON_SMALL, mNotification.getBadgeIconType());
assertEquals(SHORTCUT_ID, mNotification.getShortcutId());
assertEquals(TIMEOUT, mNotification.getTimeout());
}
diff --git a/tests/app/src/android/app/cts/ToolbarActionBarTest.java b/tests/app/src/android/app/cts/ToolbarActionBarTest.java
new file mode 100644
index 0000000..53ee982
--- /dev/null
+++ b/tests/app/src/android/app/cts/ToolbarActionBarTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.cts;
+
+import android.app.stubs.R;
+import android.app.stubs.ToolbarActivity;
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.KeyEvent;
+
+import android.view.Window;
+
+public class ToolbarActionBarTest extends ActivityInstrumentationTestCase2<ToolbarActivity> {
+
+ private ToolbarActivity mActivity;
+
+ public ToolbarActionBarTest() {
+ super(ToolbarActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ getInstrumentation().runOnMainSync(
+ () -> mActivity.getToolbar().inflateMenu(R.menu.flat_menu));
+ }
+
+ public void testOptionsMenuKey() {
+ final boolean menuIsVisible[] = {false};
+ mActivity.getActionBar().addOnMenuVisibilityListener(
+ isVisible -> menuIsVisible[0] = isVisible);
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(menuIsVisible[0]);
+ assertTrue(mActivity.getToolbar().isOverflowMenuShowing());
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+ getInstrumentation().waitForIdleSync();
+ assertFalse(menuIsVisible[0]);
+ assertFalse(mActivity.getToolbar().isOverflowMenuShowing());
+ }
+
+ public void testOpenOptionsMenu() {
+ if (!mActivity.getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
+ return;
+ }
+ final boolean menuIsVisible[] = {false};
+ mActivity.getActionBar().addOnMenuVisibilityListener(
+ isVisible -> menuIsVisible[0] = isVisible);
+ getInstrumentation().runOnMainSync(() -> mActivity.openOptionsMenu());
+ getInstrumentation().waitForIdleSync();
+ assertTrue(menuIsVisible[0]);
+ assertTrue(mActivity.getToolbar().isOverflowMenuShowing());
+ getInstrumentation().runOnMainSync(() -> mActivity.closeOptionsMenu());
+ getInstrumentation().waitForIdleSync();
+ assertFalse(menuIsVisible[0]);
+ assertFalse(mActivity.getToolbar().isOverflowMenuShowing());
+ }
+}
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index a4fc0b6..a2d3459 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -27,6 +27,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity android:name=".PreFilledLoginActivity" />
<activity android:name=".WelcomeActivity"/>
<activity android:name=".ViewAttributesTestActivity" />
<activity android:name=".AuthenticationActivity" />
diff --git a/tests/autofillservice/res/layout/login_activity.xml b/tests/autofillservice/res/layout/login_activity.xml
index d49b166..96caa46 100644
--- a/tests/autofillservice/res/layout/login_activity.xml
+++ b/tests/autofillservice/res/layout/login_activity.xml
@@ -56,8 +56,7 @@
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:inputType="textPassword"
- android:text="TopSecret" />
+ android:inputType="textPassword"/>
</LinearLayout>
<LinearLayout
diff --git a/tests/autofillservice/res/layout/pre_filled_login_activity.xml b/tests/autofillservice/res/layout/pre_filled_login_activity.xml
new file mode 100644
index 0000000..3a32928
--- /dev/null
+++ b/tests/autofillservice/res/layout/pre_filled_login_activity.xml
@@ -0,0 +1,100 @@
+<?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="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/username_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/username_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Username" />
+
+ <EditText
+ android:id="@+id/username"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="secret_agent" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/password_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Password" />
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:text="T0p S3cr3t" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/clear"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Clear" />
+
+ <Button
+ android:id="@+id/save"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Save" />
+
+ <Button
+ android:id="@+id/login"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Login" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/output"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index c5527ec..df61e37 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -124,7 +124,7 @@
}
protected static RemoteViews createPresentation(String message) {
- RemoteViews presentation = new RemoteViews(getContext()
+ final RemoteViews presentation = new RemoteViews(getContext()
.getPackageName(), R.layout.list_item);
presentation.setTextViewText(R.id.text1, message);
return presentation;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index 9b8ad9b..17cd9fc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -227,12 +227,14 @@
* </pre class="prettyprint">
*/
static class CannedDataset {
- final Map<String, AutofillValue> fields;
+ final Map<String, AutofillValue> fieldValues;
+ final Map<String, RemoteViews> fieldPresentations;
final RemoteViews presentation;
final IntentSender authentication;
private CannedDataset(Builder builder) {
- fields = builder.mFields;
+ fieldValues = builder.mFieldValues;
+ fieldPresentations = builder.mFieldPresentations;
presentation = builder.mPresentation;
authentication = builder.mAuthentication;
}
@@ -241,9 +243,12 @@
* Creates a new dataset, replacing the field ids by the real ids from the assist structure.
*/
Dataset asDataset(AssistStructure structure) {
- final Dataset.Builder builder = new Dataset.Builder(presentation);
- if (fields != null) {
- for (Map.Entry<String, AutofillValue> entry : fields.entrySet()) {
+ final Dataset.Builder builder = (presentation == null)
+ ? new Dataset.Builder()
+ : new Dataset.Builder(presentation);
+
+ if (fieldValues != null) {
+ for (Map.Entry<String, AutofillValue> entry : fieldValues.entrySet()) {
final String resourceId = entry.getKey();
final ViewNode node = findNodeByResourceId(structure, resourceId);
if (node == null) {
@@ -252,7 +257,12 @@
}
final AutofillId id = node.getAutofillId();
final AutofillValue value = entry.getValue();
- builder.setValue(id, value);
+ final RemoteViews presentation = fieldPresentations.get(resourceId);
+ if (presentation != null) {
+ builder.setValue(id, value, presentation);
+ } else {
+ builder.setValue(id, value);
+ }
}
}
builder.setAuthentication(authentication);
@@ -262,20 +272,40 @@
@Override
public String toString() {
return "CannedDataset: [hasPresentation=" + (presentation != null)
+ + ", fieldPresentations=" + (fieldPresentations)
+ ", hasAuthentication=" + (authentication != null)
- + ", fields=" + fields + "]";
+ + ", fieldValuess=" + fieldValues + "]";
}
static class Builder {
- private final Map<String, AutofillValue> mFields = new HashMap<>();
+ private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
+ private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
private RemoteViews mPresentation;
private IntentSender mAuthentication;
+ public Builder() {
+
+ }
+
+ public Builder(RemoteViews presentation) {
+ mPresentation = presentation;
+ }
+
/**
* Sets the canned value of a field based on its {@code resourceId}.
*/
public Builder setField(String resourceId, AutofillValue value) {
- mFields.put(resourceId, value);
+ mFieldValues.put(resourceId, value);
+ return this;
+ }
+
+ /**
+ * Sets the canned value of a field based on its {@code resourceId}.
+ */
+ public Builder setField(String resourceId, AutofillValue value,
+ RemoteViews presentation) {
+ setField(resourceId, value);
+ mFieldPresentations.put(resourceId, presentation);
return this;
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index d019be5..48efb32 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -76,7 +76,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.login_activity);
+ setContentView(getContentView());
mLoginButton = (Button) findViewById(R.id.login);
mSaveButton = (Button) findViewById(R.id.save);
@@ -110,6 +110,10 @@
});
}
+ protected int getContentView() {
+ return R.layout.login_activity;
+ }
+
/**
* Emulates a login action.
*/
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 3ddfa6d..280f1f1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -19,11 +19,9 @@
import static android.autofillservice.cts.Helper.ID_PASSWORD;
import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
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;
import static android.autofillservice.cts.Helper.eventually;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.Helper.runShellCommand;
@@ -184,7 +182,7 @@
// Trigger auto-fill.
mActivity.onUsername((v) -> v.requestFocus());
- final FillRequest fillRequest = sReplier.getNextFillRequest();
+ sReplier.getNextFillRequest();
final View username = mActivity.getUsername();
final View password = mActivity.getPassword();
@@ -292,7 +290,7 @@
// Trigger auto-fill.
mActivity.onUsername((v) -> v.requestFocus());
- final FillRequest fillRequest = sReplier.getNextFillRequest();
+ sReplier.getNextFillRequest();
// Auto-fill it.
sUiBot.assertNoDatasets();
@@ -426,10 +424,234 @@
sReplier.getNextFillRequest();
// Make sure all datasets are shown.
- sUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
+ final UiObject2 picker = sUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
// Auto-fill it.
- sUiBot.selectDataset(name);
+ sUiBot.selectDataset(picker, name);
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ /**
+ * Tests the scenario where the service uses custom remote views for different fields (username
+ * and password).
+ */
+ @Test
+ public void testAutofillOneDatasetCustomPresentation() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("dude"),
+ createPresentation("The Dude"))
+ .setField(ID_PASSWORD, AutofillValue.forText("sweet"),
+ createPresentation("Dude's password"))
+ .build());
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger auto-fill.
+ mActivity.onUsername((v) -> v.requestFocus());
+ sReplier.getNextFillRequest();
+
+ // Check initial field.
+ sUiBot.assertDatasets("The Dude");
+
+ // Then move around...
+ mActivity.onPassword((v) -> v.requestFocus());
+ sUiBot.assertDatasets("Dude's password");
+ mActivity.onUsername((v) -> v.requestFocus());
+ sUiBot.assertDatasets("The Dude");
+
+ // Auto-fill it.
+ mActivity.onPassword((v) -> v.requestFocus());
+ sUiBot.selectDataset("Dude's password");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ /**
+ * Tests the scenario where the service uses custom remote views for different fields (username
+ * and password) and the dataset itself, and each dataset has the same number of fields.
+ *
+ */
+ @Test
+ public void testAutofillMultipleDatasetsCustomPresentations() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder(createPresentation("Dataset1"))
+ .setField(ID_USERNAME, AutofillValue.forText("user1")) // no presentation
+ .setField(ID_PASSWORD, AutofillValue.forText("pass1"),
+ createPresentation("Pass1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("user2"),
+ createPresentation("User2"))
+ .setField(ID_PASSWORD, AutofillValue.forText("pass2")) // no presentation
+ .setPresentation(createPresentation("Dataset2"))
+ .build())
+ .build());
+ mActivity.expectAutoFill("user1", "pass1");
+
+ // Trigger auto-fill.
+ mActivity.onUsername((v) -> v.requestFocus());
+ sReplier.getNextFillRequest();
+
+ // Check initial field.
+ sUiBot.assertDatasets("Dataset1", "User2");
+
+ // Then move around...
+ mActivity.onPassword((v) -> v.requestFocus());
+ sUiBot.assertDatasets("Pass1", "Dataset2");
+ mActivity.onUsername((v) -> v.requestFocus());
+ sUiBot.assertDatasets("Dataset1", "User2");
+
+ // Auto-fill it.
+ mActivity.onPassword((v) -> v.requestFocus());
+ sUiBot.selectDataset("Pass1");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ /**
+ * Tests the scenario where the service uses custom remote views for different fields (username
+ * and password), and each dataset has the same number of fields.
+ *
+ */
+ @Test
+ public void testAutofillMultipleDatasetsCustomPresentationSameFields() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("user1"),
+ createPresentation("User1"))
+ .setField(ID_PASSWORD, AutofillValue.forText("pass1"),
+ createPresentation("Pass1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("user2"),
+ createPresentation("User2"))
+ .setField(ID_PASSWORD, AutofillValue.forText("pass2"),
+ createPresentation("Pass2"))
+ .build())
+ .build());
+ mActivity.expectAutoFill("user1", "pass1");
+
+ // Trigger auto-fill.
+ mActivity.onUsername((v) -> v.requestFocus());
+ sReplier.getNextFillRequest();
+
+ // Check initial field.
+ sUiBot.assertDatasets("User1", "User2");
+
+ // Then move around...
+ mActivity.onPassword((v) -> v.requestFocus());
+ sUiBot.assertDatasets("Pass1", "Pass2");
+ mActivity.onUsername((v) -> v.requestFocus());
+ sUiBot.assertDatasets("User1", "User2");
+
+ // Auto-fill it.
+ mActivity.onPassword((v) -> v.requestFocus());
+ sUiBot.selectDataset("Pass1");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ /**
+ * Tests the scenario where the service uses custom remote views for different fields (username
+ * and password), but each dataset has a different number of fields.
+ */
+ @Test
+ public void testAutofillMultipleDatasetsCustomPresentationFirstDatasetMissingSecondField()
+ throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("user1"),
+ createPresentation("User1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("user2"),
+ createPresentation("User2"))
+ .setField(ID_PASSWORD, AutofillValue.forText("pass2"),
+ createPresentation("Pass2"))
+ .build())
+ .build());
+ mActivity.expectAutoFill("user2", "pass2");
+
+ // Trigger auto-fill.
+ mActivity.onUsername((v) -> v.requestFocus());
+ sReplier.getNextFillRequest();
+
+ // Check initial field.
+ sUiBot.assertDatasets("User1", "User2");
+
+ // Then move around...
+ mActivity.onPassword((v) -> v.requestFocus());
+ sUiBot.assertDatasets("Pass2");
+ mActivity.onUsername((v) -> v.requestFocus());
+ sUiBot.assertDatasets("User1", "User2");
+
+ // Auto-fill it.
+ sUiBot.selectDataset("User2");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ /**
+ * Tests the scenario where the service uses custom remote views for different fields (username
+ * and password), but each dataset has a different number of fields.
+ */
+ @Test
+ public void testAutofillMultipleDatasetsCustomPresentationSecondDatasetMissingFirstField()
+ throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("user1"),
+ createPresentation("User1"))
+ .setField(ID_PASSWORD, AutofillValue.forText("pass1"),
+ createPresentation("Pass1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_PASSWORD, AutofillValue.forText("pass2"),
+ createPresentation("Pass2"))
+ .build())
+ .build());
+ mActivity.expectAutoFill("user1", "pass1");
+
+ // Trigger auto-fill.
+ mActivity.onUsername((v) -> v.requestFocus());
+ sReplier.getNextFillRequest();
+
+ // Check initial field.
+ sUiBot.assertDatasets("User1");
+
+ // Then move around...
+ mActivity.onPassword((v) -> v.requestFocus());
+ sUiBot.assertDatasets("Pass1", "Pass2");
+ mActivity.onUsername((v) -> v.requestFocus());
+ sUiBot.assertDatasets("User1");
+
+ // Auto-fill it.
+ sUiBot.selectDataset("User1");
// Check the results.
mActivity.assertAutoFilled();
@@ -768,53 +990,6 @@
}
@Test
- public void testSanitization() throws Exception {
- // Set service.
- enableService();
-
- // Set expectations.
- sReplier.addResponse(new CannedFillResponse.Builder()
- .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
- .build());
-
- // Change view contents.
- mActivity.onUsernameLabel((v) -> v.setText("DA USER"));
- mActivity.onPasswordLabel((v) -> v.setText(R.string.new_password_label));
-
- // Trigger auto-fill.
- mActivity.onUsername((v) -> v.requestFocus());
-
- // Assert sanitization on fill request:
- final FillRequest fillRequest = sReplier.getNextFillRequest();
-
- // ...dynamic text should be sanitized.
- assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
-
- // ...password label should be ok because it was set from other resource id
- assertTextOnly(findNodeByResourceId(fillRequest.structure, ID_PASSWORD_LABEL),
- "DA PASSWORD");
-
- // ...password should be ok because it came from a resource id.
- assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_PASSWORD), "TopSecret");
-
- // Trigger save
- mActivity.onUsername((v) -> v.setText("malkovich"));
- mActivity.onPassword((v) -> v.setText("malkovich"));
- mActivity.tapLogin();
-
- // Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(SAVE_DATA_TYPE_PASSWORD, true);
- final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
- // Assert sanitization on save: everything should be available!
- assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
- assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_PASSWORD_LABEL),
- "DA PASSWORD");
- assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
- assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
- }
-
- @Test
public void testDisableSelfWhenConnected() throws Exception {
enableService();
@@ -970,7 +1145,7 @@
}
@Test
- public void testManualAutofill() throws Exception {
+ public void testAutofillManuallyOneDataset() throws Exception {
// Set service.
enableService();
@@ -996,8 +1171,64 @@
final FillRequest fillRequest = sReplier.getNextFillRequest();
assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ // Should have been automatically filled.
+ sUiBot.assertNoDatasets();
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
+ public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
+ autofillManuallyTwoDatasets(true);
+ }
+
+ @Test
+ public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
+ autofillManuallyTwoDatasets(false);
+ }
+
+ private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
+ // Set service.
+ enableService();
+
+ // And activity.
+ mActivity.onUsername((v) -> {
+ v.setAutofillMode(AUTOFILL_MODE_MANUAL);
+ // TODO(b/33197203, b/33802548): setting an empty text, otherwise longPress() does not
+ // display the AUTOFILL context menu. Need to fix it, but it's a test case issue...
+ v.setText("");
+ });
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("dude"))
+ .setField(ID_PASSWORD, AutofillValue.forText("sweet"))
+ .setPresentation(createPresentation("The Dude"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("jenny"))
+ .setField(ID_PASSWORD, AutofillValue.forText("8675309"))
+ .setPresentation(createPresentation("Jenny"))
+ .build())
+ .build());
+ if (pickFirst) {
+ mActivity.expectAutoFill("dude", "sweet");
+ } else {
+ mActivity.expectAutoFill("jenny", "8675309");
+
+ }
+
+ // Long-press field to trigger AUTOFILL menu.
+ sUiBot.getAutofillMenuOption(ID_USERNAME).click();
+
+ final FillRequest fillRequest = sReplier.getNextFillRequest();
+ assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ final UiObject2 picker = sUiBot.assertDatasets("The Dude", "Jenny");
+ sUiBot.selectDataset(picker, pickFirst? "The Dude" : "Jenny");
// Check the results.
mActivity.assertAutoFilled();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivity.java
new file mode 100644
index 0000000..0c3a451
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivity.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Same as {@link LoginActivity}, but with {@code username} and {@code password} fields pre-filled.
+ */
+public class PreFilledLoginActivity extends LoginActivity {
+
+ @Override
+ protected int getContentView() {
+ return R.layout.pre_filled_login_activity;
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
new file mode 100644
index 0000000..f0e8f9f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.ID_PASSWORD;
+import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.Helper.assertTextAndValue;
+import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.Helper.assertTextOnly;
+import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.support.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Covers scenarios where the behavior is different because some fields were pre-filled.
+ */
+public class PreFilledLoginActivityTest extends AutoFillServiceTestCase {
+
+ @Rule
+ public final ActivityTestRule<PreFilledLoginActivity> mActivityRule =
+ new ActivityTestRule<PreFilledLoginActivity>(PreFilledLoginActivity.class);
+
+ private PreFilledLoginActivity mActivity;
+
+ @Before
+ public void setActivity() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @After
+ public void finishWelcomeActivity() {
+ WelcomeActivity.finishIt();
+ }
+
+ @Test
+ public void testSanitization() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+ .build());
+
+ // Change view contents.
+ mActivity.onUsernameLabel((v) -> v.setText("DA USER"));
+ mActivity.onPasswordLabel((v) -> v.setText(R.string.new_password_label));
+
+ // Trigger auto-fill.
+ mActivity.onUsername((v) -> v.requestFocus());
+
+ // Assert sanitization on fill request:
+ final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+ // ...dynamic text should be sanitized.
+ assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
+
+ // ...password label should be ok because it was set from other resource id
+ assertTextOnly(findNodeByResourceId(fillRequest.structure, ID_PASSWORD_LABEL),
+ "DA PASSWORD");
+
+ // ...username and password should be ok because they were set in the SML
+ assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_USERNAME),
+ "secret_agent");
+ assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_PASSWORD), "T0p S3cr3t");
+
+ // Trigger save
+ mActivity.onUsername((v) -> v.setText("malkovich"));
+ mActivity.onPassword((v) -> v.setText("malkovich"));
+ mActivity.tapLogin();
+
+ // Assert the snack bar is shown and tap "Save".
+ sUiBot.saveForAutofill(SAVE_DATA_TYPE_PASSWORD, true);
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+ // Assert sanitization on save: everything should be available!
+ assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
+ assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_PASSWORD_LABEL),
+ "DA PASSWORD");
+ assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
+ assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 97cc19f..8276ec8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -89,13 +89,16 @@
/**
* Asserts the dataset chooser is shown and contains the given datasets.
+ *
+ * @return the dataset picker object.
*/
- void assertDatasets(String...names) {
+ UiObject2 assertDatasets(String...names) {
final UiObject2 picker = findDatasetPicker();
for (String name : names) {
final UiObject2 dataset = picker.findObject(By.text(name));
assertWithMessage("no dataset named %s", name).that(dataset).isNotNull();
}
+ return picker;
}
/**
@@ -103,6 +106,13 @@
*/
void selectDataset(String name) {
final UiObject2 picker = findDatasetPicker();
+ selectDataset(picker, name);
+ }
+
+ /**
+ * Selects a dataset that should be visible in the floating UI.
+ */
+ void selectDataset(UiObject2 picker, String name) {
final UiObject2 dataset = picker.findObject(By.text(name));
assertWithMessage("no dataset named %s", name).that(dataset).isNotNull();
dataset.click();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
index efdb24f..8c34fe6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
@@ -67,10 +67,6 @@
mPassword = mCustomView.addLine(ID_PASSWORD_LABEL, "Password", ID_PASSWORD, BLANK_VALUE);
}
- void setSync(boolean sync) {
- mCustomView.setSync(sync);
- }
-
/**
* Sets the expectation for an auto-fill request, so it can be asserted through
* {@link #assertAutoFilled()} later.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
index 2eed0b2..12c93b6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
@@ -34,6 +34,7 @@
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
import android.support.test.rule.ActivityTestRule;
+import android.support.test.uiautomator.UiObject2;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -78,6 +79,12 @@
autofillTest(false);
}
+ @Test
+ public void testAutofillOverrideDispatchProvideAutofillStructure() throws Exception {
+ mActivity.mCustomView.setOverrideDispatchProvideAutofillStructure(true);
+ autofillTest(true);
+ }
+
/**
* Tests autofilling the virtual views, using the sync / async version of ViewStructure.addChild
*/
@@ -92,7 +99,7 @@
.setPresentation(createPresentation("The Dude"))
.build());
mActivity.expectAutoFill("dude", "sweet");
- mActivity.setSync(sync);
+ mActivity.mCustomView.setSync(sync);
// Trigger auto-fill.
mActivity.mUsername.changeFocus(true);
@@ -114,6 +121,9 @@
assertThat(passwordLabel.getClassName()).isEqualTo(LABEL_CLASS);
assertThat(password.getClassName()).isEqualTo(TEXT_CLASS);
+ assertThat(username.getIdEntry()).isEqualTo(ID_USERNAME);
+ assertThat(password.getIdEntry()).isEqualTo(ID_PASSWORD);
+
// Make sure initial focus was properly set.
assertWithMessage("Username node is not focused").that(username.isFocused()).isTrue();
assertWithMessage("Password node is focused").that(password.isFocused()).isFalse();
@@ -130,7 +140,7 @@
}
@Test
- public void testAutofillManual() throws Exception {
+ public void testAutofillManuallyOneDataset() throws Exception {
// Set service.
enableService();
@@ -147,8 +157,59 @@
mActivity.mCustomView, mActivity.mUsername.text.id, mActivity.mUsername.bounds);
sReplier.getNextFillRequest();
+ // Should have been automatically filled.
+ sUiBot.assertNoDatasets();
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+
+ // Sanity checks.
+ sReplier.assertNumberUnhandledFillRequests(0);
+ sReplier.assertNumberUnhandledSaveRequests(0);
+ }
+
+ @Test
+ public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
+ autofillManuallyTwoDatasets(true);
+ }
+
+ @Test
+ public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
+ autofillManuallyTwoDatasets(false);
+ }
+
+ private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("dude"))
+ .setField(ID_PASSWORD, AutofillValue.forText("sweet"))
+ .setPresentation(createPresentation("The Dude"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, AutofillValue.forText("jenny"))
+ .setField(ID_PASSWORD, AutofillValue.forText("8675309"))
+ .setPresentation(createPresentation("Jenny"))
+ .build())
+ .build());
+ if (pickFirst) {
+ mActivity.expectAutoFill("dude", "sweet");
+ } else {
+ mActivity.expectAutoFill("jenny", "8675309");
+
+ }
+
+ // Trigger auto-fill.
+ mActivity.getSystemService(AutofillManager.class).requestAutofill(
+ mActivity.mCustomView, mActivity.mUsername.text.id, mActivity.mUsername.bounds);
+ sReplier.getNextFillRequest();
+
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ final UiObject2 picker = sUiBot.assertDatasets("The Dude", "Jenny");
+ sUiBot.selectDataset(picker, pickFirst? "The Dude" : "Jenny");
// Check the results.
mActivity.assertAutoFilled();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
index ec50192..72b7c66 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
@@ -64,6 +64,7 @@
private int mFocusedColor;
private int mUnfocusedColor;
private boolean mSync = true;
+ private boolean mOverrideDispatchProvideAutofillStructure = false;
public VirtualContainerView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -155,24 +156,37 @@
}
@Override
+ public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) {
+ if (mOverrideDispatchProvideAutofillStructure) {
+ Log.d(TAG, "Overriding dispatchProvideAutofillStructure");
+ onProvideAutofillVirtualStructure(structure, flags);
+ } else {
+ super.dispatchProvideAutofillStructure(structure, flags);
+ }
+ }
+
+ @Override
public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+ super.onProvideAutofillVirtualStructure(structure, flags);
+
Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags);
+ super.onProvideAutofillVirtualStructure(structure, flags);
structure.setClassName(getClass().getName());
final int childrenSize = mItems.size();
int index = structure.addChildCount(childrenSize);
- final String packageName = getContext().getPackageName();
final String syncMsg = mSync ? "" : " (async)";
for (int i = 0; i < childrenSize; i++) {
final Item item = mItems.valueAt(i);
Log.d(TAG, "Adding new child" + syncMsg + " at index " + index + ": " + item);
final ViewStructure child = mSync
- ? structure.newChild(index, item.id, 0)
- : structure.asyncNewChild(index, item.id, 0);
+ ? structure.newChild(index)
+ : structure.asyncNewChild(index);
+ child.setAutofillId(structure, item.id);
child.setDataIsSensitive(item.sensitive);
index++;
final String className = item.editable ? TEXT_CLASS : LABEL_CLASS;
child.setClassName(className);
- child.setId(1000 + index, packageName, "id", item.resourceId);
+ child.setIdEntry(item.resourceId);
child.setText(item.text);
child.setAutofillValue(AutofillValue.forText(item.text));
child.setFocused(item.line.focused);
@@ -196,6 +210,10 @@
mSync = sync;
}
+ void setOverrideDispatchProvideAutofillStructure(boolean flag) {
+ mOverrideDispatchProvideAutofillStructure = flag;
+ }
+
private static int nextId;
final class Line {
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverTest.java b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
index fabb4af..9f690ee 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
@@ -25,6 +25,7 @@
import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
import android.database.Cursor;
+import android.database.PageViewCursor;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Bundle;
@@ -425,7 +426,7 @@
* Verifies that paging arguments are handled correctly
* when the provider supports paging.
*/
- public void testQuery_PagingSupport() {
+ public void testQuery_InProcessProvider_NoAutoPaging() {
mContentResolver.delete(TABLE1_URI, null, null);
ContentValues values = new ContentValues();
@@ -442,8 +443,43 @@
mCursor = mContentResolver.query(TABLE1_URI, null, queryArgs, null);
int col = mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME);
+ Bundle extras = mCursor.getExtras();
+ extras = extras != null ? extras : Bundle.EMPTY;
+
+ assertEquals(100, mCursor.getCount());
+ assertFalse(extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE));
+
+ mCursor.close();
+ }
+
+ /**
+ * Verifies that paging arguments are handled correctly
+ * when the provider supports paging.
+ */
+ public void testQuery_OutOfProcessProvider_AutoPaging() {
+
+ mContentResolver.delete(REMOTE_TABLE1_URI, null, null);
+ ContentValues values = new ContentValues();
+
+ for (int i = 0; i < 100; i++) {
+ values.put(COLUMN_KEY_NAME, i);
+ mContentResolver.insert(REMOTE_TABLE1_URI, values);
+ }
+
+ Bundle queryArgs = new Bundle();
+ queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 10);
+ queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 3);
+
+ mCursor = mContentResolver.query(REMOTE_TABLE1_URI, null, queryArgs, null);
+
+ Bundle extras = mCursor.getExtras();
+ extras = extras != null ? extras : Bundle.EMPTY;
+
assertEquals(3, mCursor.getCount());
- assertEquals(100, mCursor.getExtras().getInt(ContentResolver.EXTRA_TOTAL_SIZE));
+ assertTrue(extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE));
+ assertEquals(100, extras.getInt(ContentResolver.EXTRA_TOTAL_SIZE));
+
+ int col = mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME);
mCursor.moveToNext();
assertEquals(10, mCursor.getInt(col));
diff --git a/tests/tests/content/src/android/content/cts/MockContentProvider.java b/tests/tests/content/src/android/content/cts/MockContentProvider.java
index 241e30f..b6eb804 100644
--- a/tests/tests/content/src/android/content/cts/MockContentProvider.java
+++ b/tests/tests/content/src/android/content/cts/MockContentProvider.java
@@ -255,22 +255,6 @@
}
@Override
- public Cursor query(Uri uri, String[] projection, Bundle queryArgs,
- CancellationSignal cancellationSignal) {
- Cursor c = super.query(uri, projection, queryArgs, cancellationSignal);
-
- queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
- int offset = queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0);
- int limit = queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, 0);
-
- if (offset > 0 || limit > 0) {
- return new PageViewCursor(c, offset, limit);
- }
-
- return c;
- }
-
- @Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder, null);
@@ -467,114 +451,4 @@
private static File getCrashOnLaunchFile(Context context) {
return context.getFileStreamPath("MockContentProvider.crashonlaunch");
}
-
- /**
- * Cursor wrapper that provides visibility into a subset of a wrapped cursor.
- *
- * The window is specified by offset and limit.
- */
- private static final class PageViewCursor extends CursorWrapper {
-
- private final int mOffset; // aka first index
- private final int mCount;
- private final Bundle mExtras;
-
- private int mPos = -1;
-
- PageViewCursor(Cursor cursor, int offset, int limit) {
- super(cursor);
-
- checkArgument(offset > -1);
- checkArgument(limit > -1);
-
- mOffset = offset;
-
- Bundle extras = cursor.getExtras();
-
- // We need a mutable bundle so we can add QUERY_RESULT_SIZE.
- // Direct equality check is correct here. Bundle.EMPTY is a specific instance
- // of Bundle that is immutable by way of implementation.
- mExtras = (extras == Bundle.EMPTY) ? new Bundle() : extras;
-
- // When we're wrapping another cursor, it should not already be "paged".
- checkArgument(!mExtras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE));
-
- int count = mCursor.getCount();
- checkArgument(offset < count);
-
- mExtras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, count);
- mCount = (limit > count - offset) ? count - offset : limit;
- }
-
- @Override
- public Bundle getExtras() {
- return mExtras;
- }
-
- @Override
- public int getPosition() {
- return mPos;
- }
-
- @Override
- public boolean moveToFirst() {
- return moveToPosition(0);
- }
-
- @Override
- public boolean moveToLast() {
- return moveToPosition(mCount - 1);
- }
-
- @Override
- public boolean moveToNext() {
- return move(1);
- }
-
- @Override
- public boolean moveToPrevious() {
- return move(-1);
- }
-
- @Override
- public boolean move(int offset) {
- return moveToPosition(mPos + offset);
- }
-
- @Override
- public boolean moveToPosition(int position) {
- if (position > mCount - 1) {
- mPos = mOffset + mCount;
- super.moveToPosition(mPos);
- return false;
- }
-
- // Make sure position isn't before the beginning of the cursor
- if (position < 0) {
- mPos = -1;
- super.moveToPosition(-1);
- return false;
- }
-
- int oldPosition = mPos;
- // Check for no-op moves, and skip the rest of the work for them.
- if (position == oldPosition) {
- return true;
- }
-
- if (super.moveToPosition(position + mOffset)) {
- mPos = position;
- return true;
- } else {
- mPos = -1;
- super.moveToPosition(-1);
- return false;
- }
- }
-
- @Override
- public int getCount() {
- return mCount;
- }
- }
}
diff --git a/tests/tests/graphics/assets/samplefont2.ttf b/tests/tests/graphics/assets/samplefont2.ttf
new file mode 100644
index 0000000..257d26e
--- /dev/null
+++ b/tests/tests/graphics/assets/samplefont2.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/samplefont2.ttx b/tests/tests/graphics/assets/samplefont2.ttx
new file mode 100644
index 0000000..9af91c3
--- /dev/null
+++ b/tests/tests/graphics/assets/samplefont2.ttx
@@ -0,0 +1,176 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="1.0"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0061" name=".notdef" />
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/samplefont3.ttf b/tests/tests/graphics/assets/samplefont3.ttf
new file mode 100644
index 0000000..4d513cb
--- /dev/null
+++ b/tests/tests/graphics/assets/samplefont3.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/samplefont3.ttx b/tests/tests/graphics/assets/samplefont3.ttx
new file mode 100644
index 0000000..f58925b
--- /dev/null
+++ b/tests/tests/graphics/assets/samplefont3.ttx
@@ -0,0 +1,176 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:31:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="1.0"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0061" name=".notdef" />
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
index 28e9236..01c445c 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
index b27bdbd..739eea1 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
index cdae7fa..9a5efd2 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png
new file mode 100644
index 0000000..2edc3c7
--- /dev/null
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png
index d3c5975..0717399 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png
index 5308c7b..505aa2e 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png
index e507b53..9b53e94 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/vector_icon_scale_3.xml b/tests/tests/graphics/res/drawable/vector_icon_scale_3.xml
new file mode 100644
index 0000000..74fa475
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/vector_icon_scale_3.xml
@@ -0,0 +1,45 @@
+<!--
+ ~ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp" >
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
+ </group>
+ <group
+ android:scaleX="200"
+ android:scaleY="200" >
+ <group>
+ <path
+ android:name="twoLines"
+ android:fillColor="#FFFF0000"
+ android:pathData="M 0.75, 0.25 l 0, 0.5, -0.5, 0 z"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="0.05" />
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
index 49c1ea1..db4b04d 100644
--- a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
@@ -37,6 +37,7 @@
import org.junit.runner.RunWith;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -291,4 +292,98 @@
assertTrue(0 > Typeface.FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES);
assertTrue(0 > Typeface.FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
}
+
+ @Test
+ public void testTypefaceBuilder_AssetSource() {
+ Typeface.Builder builder = Typeface.Builder.obtain();
+ try {
+ Typeface typeface1 =
+ builder.setSourceFromAsset(mContext.getAssets(), "samplefont.ttf").build();
+ assertNotNull(typeface1);
+
+ builder.reset();
+ Typeface typeface2 =
+ builder.setSourceFromAsset(mContext.getAssets(), "samplefont.ttf").build();
+ assertNotNull(typeface2);
+ assertSame("Same font asset should return same Typeface object", typeface1, typeface2);
+
+ builder.reset();
+ Typeface typeface3 =
+ builder.setSourceFromAsset(mContext.getAssets(), "samplefont2.ttf").build();
+ assertNotNull(typeface3);
+ assertNotSame("Different font asset should return different Typeface object",
+ typeface2, typeface3);
+
+ builder.reset();
+ Typeface typeface4 =
+ builder.setSourceFromAsset(mContext.getAssets(), "samplefont3.ttf").build();
+ assertNotNull(typeface4);
+ assertNotSame("Different font asset should return different Typeface object",
+ typeface2, typeface4);
+ assertNotSame("Different font asset should return different Typeface object",
+ typeface3, typeface4);
+
+ builder.reset();
+ Typeface typeface5 =
+ builder.setSourceFromAsset(mContext.getAssets(), "samplefont.ttf")
+ .setFontVariationSettings("'wdth' 1.0").build();
+ assertNotNull(typeface5);
+ assertNotSame("Different font font variation should return different Typeface object",
+ typeface2, typeface5);
+
+ builder.reset();
+ Typeface typeface6 =
+ builder.setSourceFromAsset(mContext.getAssets(), "samplefont.ttf")
+ .setFontVariationSettings("'wdth' 2.0").build();
+ assertNotNull(typeface6);
+ assertNotSame("Different font font variation should return different Typeface object",
+ typeface2, typeface6);
+ assertNotSame("Different font font variation should return different Typeface object",
+ typeface5, typeface6);
+
+ // TODO: Add ttc index case. Need TTC file for CTS.
+ } finally {
+ builder.recycle();
+ }
+ }
+
+ @Test
+ public void testTypefaceBuilder_FileSource() {
+ Typeface.Builder builder = Typeface.Builder.obtain();
+ try {
+ File file = new File(obtainPath());
+ Typeface typeface1 = builder.setSourceFromFile(file).build();
+ assertNotNull(typeface1);
+
+ builder.reset();
+ Typeface typeface2 = builder.setSourceFromFilePath(file.getAbsolutePath()).build();
+ assertNotNull(typeface2);
+
+ builder.reset();
+ Typeface typeface3 = builder.setSourceFromFile(file)
+ .setFontVariationSettings("'wdth' 1.0")
+ .build();
+ assertNotNull(typeface3);
+ assertNotSame(typeface1, typeface3);
+ assertNotSame(typeface2, typeface3);
+
+ // TODO: Add ttc index case. Need TTC file for CTS.
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ builder.recycle();
+ }
+ }
+
+ @Test
+ public void testTypefaceBuilder_FileSourceFD() {
+ Typeface.Builder builder = Typeface.Builder.obtain();
+ try (FileInputStream fis = new FileInputStream(obtainPath())) {
+ assertNotNull(builder.setSourceFromFile(fis.getFD()).build());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ builder.recycle();
+ }
+ }
}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
index d2618d9..16c6961 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
@@ -101,6 +101,7 @@
R.drawable.vector_icon_stroke_3,
R.drawable.vector_icon_scale_1,
R.drawable.vector_icon_scale_2,
+ R.drawable.vector_icon_scale_3,
R.drawable.vector_icon_group_clip,
};
@@ -118,6 +119,7 @@
R.drawable.vector_icon_stroke_3_golden,
R.drawable.vector_icon_scale_1_golden,
R.drawable.vector_icon_scale_2_golden,
+ R.drawable.vector_icon_scale_3_golden,
R.drawable.vector_icon_group_clip_golden,
};
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index d2953b3..b48db78 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -756,7 +756,7 @@
}
public void testMuteDndUnaffectedStreams() throws Exception {
- if (mUseFixedVolume) {
+ if (mUseFixedVolume || mIsTelevision) {
return;
}
int[] streams = {
diff --git a/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java b/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java
index 5eccc0d..b5e2f77 100644
--- a/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/PpsMoParserTest.java
@@ -73,110 +73,193 @@
PasspointConfiguration config = new PasspointConfiguration();
config.setUpdateIdentifier(12);
+ assertEquals(12, config.getUpdateIdentifier());
config.setCredentialPriority(99);
+ assertEquals(99, config.getCredentialPriority());
// AAA Server trust root.
Map<String, byte[]> trustRootCertList = new HashMap<>();
trustRootCertList.put("server1.trust.root.com", certFingerprint);
config.setTrustRootCertList(trustRootCertList);
+ assertEquals(trustRootCertList, config.getTrustRootCertList());
// Subscription update.
UpdateParameter subscriptionUpdate = new UpdateParameter();
subscriptionUpdate.setUpdateIntervalInMinutes(120);
+ assertEquals(120, subscriptionUpdate.getUpdateIntervalInMinutes());
subscriptionUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_SSP);
+ assertEquals(UpdateParameter.UPDATE_METHOD_SSP, subscriptionUpdate.getUpdateMethod());
subscriptionUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER);
+ assertEquals(UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER,
+ subscriptionUpdate.getRestriction());
subscriptionUpdate.setServerUri("subscription.update.com");
+ assertEquals("subscription.update.com", subscriptionUpdate.getServerUri());
subscriptionUpdate.setUsername("subscriptionUser");
+ assertEquals("subscriptionUser", subscriptionUpdate.getUsername());
subscriptionUpdate.setBase64EncodedPassword("subscriptionPass");
+ assertEquals("subscriptionPass", subscriptionUpdate.getBase64EncodedPassword());
subscriptionUpdate.setTrustRootCertUrl("subscription.update.cert.com");
+ assertEquals("subscription.update.cert.com", subscriptionUpdate.getTrustRootCertUrl());
subscriptionUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+ assertTrue(Arrays.equals(certFingerprint,
+ subscriptionUpdate.getTrustRootCertSha256Fingerprint()));
config.setSubscriptionUpdate(subscriptionUpdate);
+ assertEquals(subscriptionUpdate, config.getSubscriptionUpdate());
// Subscription parameters.
config.setSubscriptionCreationTimeInMs(format.parse("2016-02-01T10:00:00Z").getTime());
+ assertEquals(format.parse("2016-02-01T10:00:00Z").getTime(),
+ config.getSubscriptionCreationTimeInMs());
config.setSubscriptionExpirationTimeInMs(format.parse("2016-03-01T10:00:00Z").getTime());
+ assertEquals(format.parse("2016-03-01T10:00:00Z").getTime(),
+ config.getSubscriptionExpirationTimeInMs());
config.setSubscriptionType("Gold");
+ assertEquals("Gold", config.getSubscriptionType());
config.setUsageLimitDataLimit(921890);
+ assertEquals(921890, config.getUsageLimitDataLimit());
config.setUsageLimitStartTimeInMs(format.parse("2016-12-01T10:00:00Z").getTime());
+ assertEquals(format.parse("2016-12-01T10:00:00Z").getTime(),
+ config.getUsageLimitStartTimeInMs());
config.setUsageLimitTimeLimitInMinutes(120);
+ assertEquals(120, config.getUsageLimitTimeLimitInMinutes());
config.setUsageLimitUsageTimePeriodInMinutes(99910);
+ assertEquals(99910, config.getUsageLimitUsageTimePeriodInMinutes());
// HomeSP configuration.
HomeSp homeSp = new HomeSp();
homeSp.setFriendlyName("Century House");
+ assertEquals("Century House", homeSp.getFriendlyName());
homeSp.setFqdn("mi6.co.uk");
+ assertEquals("mi6.co.uk", homeSp.getFqdn());
homeSp.setRoamingConsortiumOis(new long[] {0x112233L, 0x445566L});
+ assertTrue(Arrays.equals(new long[] {0x112233L, 0x445566L},
+ homeSp.getRoamingConsortiumOis()));
homeSp.setIconUrl("icon.test.com");
+ assertEquals("icon.test.com", homeSp.getIconUrl());
Map<String, Long> homeNetworkIds = new HashMap<>();
homeNetworkIds.put("TestSSID", 0x12345678L);
homeNetworkIds.put("NullHESSID", null);
homeSp.setHomeNetworkIds(homeNetworkIds);
+ assertEquals(homeNetworkIds, homeSp.getHomeNetworkIds());
homeSp.setMatchAllOis(new long[] {0x11223344});
+ assertTrue(Arrays.equals(new long[] {0x11223344}, homeSp.getMatchAllOis()));
homeSp.setMatchAnyOis(new long[] {0x55667788});
+ assertTrue(Arrays.equals(new long[] {0x55667788}, homeSp.getMatchAnyOis()));
homeSp.setOtherHomePartners(new String[] {"other.fqdn.com"});
+ assertTrue(Arrays.equals(new String[] {"other.fqdn.com"},
+ homeSp.getOtherHomePartners()));
config.setHomeSp(homeSp);
+ assertEquals(homeSp, config.getHomeSp());
// Credential configuration.
Credential credential = new Credential();
credential.setCreationTimeInMs(format.parse("2016-01-01T10:00:00Z").getTime());
+ assertEquals(format.parse("2016-01-01T10:00:00Z").getTime(),
+ credential.getCreationTimeInMs());
credential.setExpirationTimeInMs(format.parse("2016-02-01T10:00:00Z").getTime());
+ assertEquals(format.parse("2016-02-01T10:00:00Z").getTime(),
+ credential.getExpirationTimeInMs());
credential.setRealm("shaken.stirred.com");
+ assertEquals("shaken.stirred.com", credential.getRealm());
credential.setCheckAaaServerCertStatus(true);
+ assertTrue(credential.getCheckAaaServerCertStatus());
Credential.UserCredential userCredential = new Credential.UserCredential();
userCredential.setUsername("james");
+ assertEquals("james", userCredential.getUsername());
userCredential.setPassword("Ym9uZDAwNw==");
+ assertEquals("Ym9uZDAwNw==", userCredential.getPassword());
userCredential.setMachineManaged(true);
+ assertTrue(userCredential.getMachineManaged());
userCredential.setSoftTokenApp("TestApp");
+ assertEquals("TestApp", userCredential.getSoftTokenApp());
userCredential.setAbleToShare(true);
+ assertTrue(userCredential.getAbleToShare());
userCredential.setEapType(21);
+ assertEquals(21, userCredential.getEapType());
userCredential.setNonEapInnerMethod("MS-CHAP-V2");
+ assertEquals("MS-CHAP-V2", userCredential.getNonEapInnerMethod());
credential.setUserCredential(userCredential);
+ assertEquals(userCredential, credential.getUserCredential());
Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
certCredential.setCertType("x509v3");
+ assertEquals("x509v3", certCredential.getCertType());
certCredential.setCertSha256Fingerprint(certFingerprint);
+ assertTrue(Arrays.equals(certFingerprint, certCredential.getCertSha256Fingerprint()));
credential.setCertCredential(certCredential);
+ assertEquals(certCredential, credential.getCertCredential());
Credential.SimCredential simCredential = new Credential.SimCredential();
simCredential.setImsi("imsi");
+ assertEquals("imsi", simCredential.getImsi());
simCredential.setEapType(24);
+ assertEquals(24, simCredential.getEapType());
credential.setSimCredential(simCredential);
+ assertEquals(simCredential, credential.getSimCredential());
config.setCredential(credential);
+ assertEquals(credential, config.getCredential());
// Policy configuration.
Policy policy = new Policy();
List<Policy.RoamingPartner> preferredRoamingPartnerList = new ArrayList<>();
Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
partner1.setFqdn("test1.fqdn.com");
+ assertEquals("test1.fqdn.com", partner1.getFqdn());
partner1.setFqdnExactMatch(true);
+ assertTrue(partner1.getFqdnExactMatch());
partner1.setPriority(127);
+ assertEquals(127, partner1.getPriority());
partner1.setCountries("us,fr");
+ assertEquals("us,fr", partner1.getCountries());
Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
partner2.setFqdn("test2.fqdn.com");
+ assertEquals("test2.fqdn.com", partner2.getFqdn());
partner2.setFqdnExactMatch(false);
+ assertFalse(partner2.getFqdnExactMatch());
partner2.setPriority(200);
+ assertEquals(200, partner2.getPriority());
partner2.setCountries("*");
+ assertEquals("*", partner2.getCountries());
preferredRoamingPartnerList.add(partner1);
preferredRoamingPartnerList.add(partner2);
policy.setPreferredRoamingPartnerList(preferredRoamingPartnerList);
+ assertEquals(preferredRoamingPartnerList, policy.getPreferredRoamingPartnerList());
policy.setMinHomeDownlinkBandwidth(23412);
+ assertEquals(23412, policy.getMinHomeDownlinkBandwidth());
policy.setMinHomeUplinkBandwidth(9823);
+ assertEquals(9823, policy.getMinHomeUplinkBandwidth());
policy.setMinRoamingDownlinkBandwidth(9271);
+ assertEquals(9271, policy.getMinRoamingDownlinkBandwidth());
policy.setMinRoamingUplinkBandwidth(2315);
+ assertEquals(2315, policy.getMinRoamingUplinkBandwidth());
policy.setExcludedSsidList(new String[] {"excludeSSID"});
+ assertTrue(Arrays.equals(new String[] {"excludeSSID"}, policy.getExcludedSsidList()));
Map<Integer, String> requiredProtoPortMap = new HashMap<>();
requiredProtoPortMap.put(12, "34,92,234");
policy.setRequiredProtoPortMap(requiredProtoPortMap);
+ assertEquals(requiredProtoPortMap, policy.getRequiredProtoPortMap());
policy.setMaximumBssLoadValue(23);
+ assertEquals(23, policy.getMaximumBssLoadValue());
UpdateParameter policyUpdate = new UpdateParameter();
policyUpdate.setUpdateIntervalInMinutes(120);
+ assertEquals(120, policyUpdate.getUpdateIntervalInMinutes());
policyUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_OMADM);
+ assertEquals(UpdateParameter.UPDATE_METHOD_OMADM, policyUpdate.getUpdateMethod());
policyUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_HOMESP);
+ assertEquals(UpdateParameter.UPDATE_RESTRICTION_HOMESP, policyUpdate.getRestriction());
policyUpdate.setServerUri("policy.update.com");
+ assertEquals("policy.update.com", policyUpdate.getServerUri());
policyUpdate.setUsername("updateUser");
+ assertEquals("updateUser", policyUpdate.getUsername());
policyUpdate.setBase64EncodedPassword("updatePass");
+ assertEquals("updatePass", policyUpdate.getBase64EncodedPassword());
policyUpdate.setTrustRootCertUrl("update.cert.com");
+ assertEquals("update.cert.com", policyUpdate.getTrustRootCertUrl());
policyUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+ assertTrue(Arrays.equals(certFingerprint,
+ policyUpdate.getTrustRootCertSha256Fingerprint()));
policy.setPolicyUpdate(policyUpdate);
+ assertEquals(policyUpdate, policy.getPolicyUpdate());
config.setPolicy(policy);
+ assertEquals(policy, config.getPolicy());
return config;
}
diff --git a/tests/tests/provider/src/android/provider/cts/GetResultActivity.java b/tests/tests/provider/src/android/provider/cts/GetResultActivity.java
index 566a7d6..0edc617 100644
--- a/tests/tests/provider/src/android/provider/cts/GetResultActivity.java
+++ b/tests/tests/provider/src/android/provider/cts/GetResultActivity.java
@@ -25,7 +25,7 @@
import java.util.concurrent.TimeUnit;
public class GetResultActivity extends Activity {
- private final SynchronousQueue<Result> mResult = new SynchronousQueue<>();
+ private static SynchronousQueue<Result> sResult;
public static class Result {
public final int requestCode;
@@ -51,16 +51,20 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
try {
- mResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+ sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
+ public void clearResult() {
+ sResult = new SynchronousQueue<>();
+ }
+
public Result getResult() {
final Result result;
try {
- result = mResult.poll(30, TimeUnit.SECONDS);
+ result = sResult.poll(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
@@ -72,7 +76,7 @@
public Result getResult(long timeout, TimeUnit unit) {
try {
- return mResult.poll(timeout, unit);
+ return sResult.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 73b5559..1295dd4 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreUiTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreUiTest.java
@@ -72,6 +72,7 @@
final Context context = getInstrumentation().getContext();
mActivity = launchActivity(context.getPackageName(), GetResultActivity.class, null);
+ mActivity.clearResult();
}
@Override
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
index d0db60d..621d973 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
@@ -16,8 +16,6 @@
package android.provider.cts;
-import android.provider.cts.R;
-
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -29,8 +27,8 @@
import android.os.SystemClock;
import android.provider.MediaStore;
import android.provider.MediaStore.MediaColumns;
+import android.provider.MediaStore.Files.FileColumns;
import android.test.AndroidTestCase;
-import android.util.Log;
import com.android.compatibility.common.util.FileCopyHelper;
@@ -40,7 +38,6 @@
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -65,6 +62,10 @@
final String testName = getClass().getCanonicalName();
mResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
"_data LIKE ?1", new String[] {"%" + testName + "%"});
+
+ mResolver.delete(MediaStore.Files.getContentUri("external"),
+ "_data LIKE ?1", new String[] {"%" + testName + "%"});
+
File ext = Environment.getExternalStorageDirectory();
File[] junk = ext.listFiles(new FilenameFilter() {
@@ -482,6 +483,36 @@
return paths;
}
+ public void testUpdateMediaType() throws Exception {
+ String fileDir = Environment.getExternalStorageDirectory() +
+ "/" + getClass().getCanonicalName();
+ String fileName = fileDir + "/test.mp3";
+ writeFile(R.raw.testmp3, fileName);
+
+ String volumeName = MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME;
+ Uri allFilesUri = MediaStore.Files.getContentUri(volumeName);
+ ContentValues values = new ContentValues();
+ values.put(MediaColumns.DATA, fileName);
+ values.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_AUDIO);
+ Uri fileUri = mResolver.insert(allFilesUri, values);
+
+
+ // There is special logic in MediaProvider#update() to update paths when a folder was moved
+ // or renamed. It only checks whether newValues only has one column but assumes the provided
+ // column is _data. We need to guard the case where there is only one column in newValues
+ // and it's not _data.
+ ContentValues newValues = new ContentValues(1);
+ newValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_NONE);
+ mResolver.update(fileUri, newValues, null, null);
+
+ try (Cursor c = mResolver.query(
+ fileUri, new String[] { FileColumns.MEDIA_TYPE }, null, null, null)) {
+ c.moveToNext();
+ assertEquals(FileColumns.MEDIA_TYPE_NONE,
+ c.getInt(c.getColumnIndex(FileColumns.MEDIA_TYPE)));
+ }
+ }
+
private static File[] dropFirst(File[] before) {
final File[] after = new File[before.length - 1];
System.arraycopy(before, 1, after, 0, after.length);
diff --git a/tests/tests/security/res/raw/bug_35467107.mp4 b/tests/tests/security/res/raw/bug_35467107.mp4
index e69de29..43ccef6 100644
--- a/tests/tests/security/res/raw/bug_35467107.mp4
+++ b/tests/tests/security/res/raw/bug_35467107.mp4
Binary files differ
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index a78da07..b80ef3a 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -187,6 +187,10 @@
<activity android:name="android.view.cts.MenuTestActivity"
android:label="MenuTestActivity" />
+ <activity android:name="android.view.cts.MenuItemCtsActivity"
+ android:theme="@android:style/Theme.Material.Light.NoActionBar"
+ android:label="MenuItemCtsActivity" />
+
<activity android:name="android.view.cts.ActionModeCtsActivity"
android:label="ActionModeCtsActivity">
</activity>
diff --git a/tests/tests/view/res/layout/menu_item_layout.xml b/tests/tests/view/res/layout/menu_item_layout.xml
new file mode 100644
index 0000000..cb6d146
--- /dev/null
+++ b/tests/tests/view/res/layout/menu_item_layout.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <Toolbar
+ android:id="@+id/toolbar_main"
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/actionBarSize" />
+
+</LinearLayout>
diff --git a/tests/tests/view/res/menu/menu_regular.xml b/tests/tests/view/res/menu/menu_regular.xml
new file mode 100644
index 0000000..89d31d6
--- /dev/null
+++ b/tests/tests/view/res/menu/menu_regular.xml
@@ -0,0 +1,52 @@
+<?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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@+id/menu_first"
+ android:title="@string/menu1"
+ android:icon="@drawable/icon_blue"
+ android:iconTint="@android:color/white"
+ android:showAsAction="always"/>
+
+ <item
+ android:id="@+id/menu_second"
+ android:title="@string/menu2"
+ android:icon="@drawable/icon_black"
+ android:iconTintMode="screen"
+ android:showAsAction="always"/>
+
+ <item
+ android:id="@+id/menu_third"
+ android:title="@string/menu3"
+ android:icon="@drawable/icon_green"/>
+
+ <item
+ android:id="@+id/menu_fourth"
+ android:title="@string/menu4"
+ android:icon="@drawable/icon_red"/>
+
+ <item
+ android:id="@+id/menu_fifth"
+ android:title="@string/menu5"
+ android:icon="@drawable/icon_yellow"/>
+
+ <item
+ android:id="@+id/menu_sixth"
+ android:title="@string/menu6"/>
+
+</menu>
diff --git a/tests/tests/view/res/values/strings.xml b/tests/tests/view/res/values/strings.xml
index c167278..52478b5 100644
--- a/tests/tests/view/res/values/strings.xml
+++ b/tests/tests/view/res/values/strings.xml
@@ -176,4 +176,11 @@
text, I would love to see the kind of devices you guys now use! Guys, maybe some devices need longer string!
I think so, so how about double this string, like copy and paste! </string>
<string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
+
+ <string name="menu1">menu one</string>
+ <string name="menu2">menu two</string>
+ <string name="menu3">menu three</string>
+ <string name="menu4">menu four</string>
+ <string name="menu5">menu five</string>
+ <string name="menu6">menu six</string>
</resources>
diff --git a/tests/tests/view/src/android/view/cts/MenuItemCtsActivity.java b/tests/tests/view/src/android/view/cts/MenuItemCtsActivity.java
new file mode 100755
index 0000000..fa07d5c
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/MenuItemCtsActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.widget.Toolbar;
+
+/**
+ * Stub activity for testing {@link MenuItem}
+ */
+public class MenuItemCtsActivity extends Activity {
+ private Toolbar mMainToolbar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.menu_item_layout);
+
+ mMainToolbar = (Toolbar) findViewById(R.id.toolbar_main);
+ setActionBar(mMainToolbar);
+ }
+
+ public Toolbar getMainToolbar() {
+ return mMainToolbar;
+ }
+}
+
diff --git a/tests/tests/view/src/android/view/cts/MenuItemTest.java b/tests/tests/view/src/android/view/cts/MenuItemTest.java
new file mode 100644
index 0000000..fc0ae0b
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/MenuItemTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import android.app.Instrumentation;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MenuItemTest {
+ private Instrumentation mInstrumentation;
+ private MenuItemCtsActivity mActivity;
+ private Menu mMenu;
+
+ @Rule
+ public ActivityTestRule<MenuItemCtsActivity> mActivityRule =
+ new ActivityTestRule<>(MenuItemCtsActivity.class);
+
+
+ @UiThreadTest
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+
+ mActivity.getMainToolbar().inflateMenu(R.menu.menu_regular);
+ mMenu = mActivity.getMainToolbar().getMenu();
+ }
+
+ @Test
+ public void testAccessThumbTint() {
+ // Note that this test is not marked as @UiThreadTest. Updating MenuItem does not
+ // immediately update the displayed content, and even though the getters are expected
+ // to immediately return the just-set value, using instrumentation to wait for the
+ // update to propagate makes this test more in line with the "real" application
+ // experience.
+ MenuItem firstItem = mMenu.getItem(0);
+ MenuItem secondItem = mMenu.getItem(1);
+ MenuItem thirdItem = mMenu.getItem(2);
+
+ // These are the default set in layout XML
+ assertEquals(Color.WHITE, firstItem.getIconTintList().getDefaultColor());
+ assertNull(firstItem.getIconTintMode());
+ assertNull(secondItem.getIconTintList());
+ assertEquals(PorterDuff.Mode.SCREEN, secondItem.getIconTintMode());
+ assertNull(thirdItem.getIconTintList());
+ assertNull(thirdItem.getIconTintMode());
+
+ // Change tint color list and mode and verify that they are returned by the getters
+ ColorStateList colors = ColorStateList.valueOf(Color.RED);
+ mInstrumentation.runOnMainSync(() -> {
+ firstItem.setIconTintList(colors);
+ firstItem.setIconTintMode(PorterDuff.Mode.XOR);
+ });
+ mInstrumentation.waitForIdleSync();
+ assertSame(colors, firstItem.getIconTintList());
+ assertEquals(PorterDuff.Mode.XOR, firstItem.getIconTintMode());
+
+ // Ensure the tint is preserved across drawable changes.
+ mInstrumentation.runOnMainSync(() -> firstItem.setIcon(R.drawable.icon_yellow));
+ mInstrumentation.waitForIdleSync();
+ assertSame(colors, firstItem.getIconTintList());
+ assertEquals(PorterDuff.Mode.XOR, firstItem.getIconTintMode());
+
+ // Change tint color list and mode again and verify that they are returned by the getters
+ ColorStateList colorsNew = ColorStateList.valueOf(Color.MAGENTA);
+ mInstrumentation.runOnMainSync(() -> {
+ firstItem.setIconTintList(colorsNew);
+ firstItem.setIconTintMode(PorterDuff.Mode.SRC_IN);
+ });
+ mInstrumentation.waitForIdleSync();
+ assertSame(colorsNew, firstItem.getIconTintList());
+ assertEquals(PorterDuff.Mode.SRC_IN, firstItem.getIconTintMode());
+ }
+}
diff --git a/tools/cts-tradefed/res/config/cts-pdk.xml b/tools/cts-tradefed/res/config/cts-pdk.xml
index f403b8b..7c06849 100644
--- a/tools/cts-tradefed/res/config/cts-pdk.xml
+++ b/tools/cts-tradefed/res/config/cts-pdk.xml
@@ -13,28 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<configuration description="Runs Tests useful on validating a PDK fusion build">
+<configuration description="Runs a subset of CTS tests for the Treble 'CTS on AOSP' requirement">
<include name="cts" />
<option name="plan" value="cts-pdk" />
<!-- Include test modules -->
- <option name="compatibility:include-filter" value="CtsAadbHostTestCases" />
- <option name="compatibility:include-filter" value="CtsBluetoothTestCases" />
- <option name="compatibility:include-filter" value="CtsGraphicsTestCases" />
- <option name="compatibility:include-filter" value="CtsGraphics2TestCases" />
- <option name="compatibility:include-filter" value="CtsHardwareTestCases" />
- <option name="compatibility:include-filter" value="CtsMediaTestCases" />
- <option name="compatibility:include-filter" value="CtsNetTestCases" />
- <option name="compatibility:include-filter" value="CtsDeqpTestCases" />
- <option name="compatibility:include-filter" value="CtsRenderscriptTestCases" />
- <option name="compatibility:include-filter" value="CtsRenderscriptLegacyTestCases" />
- <option name="compatibility:include-filter" value="CtsSensorTestCases" />
- <option name="compatibility:include-filter" value="CtsTelephonyTestCases" />
- <option name="compatibility:include-filter" value="CtsTelephony2TestCases" />
- <option name="compatibility:include-filter" value="CtsRsBlasTestCases" />
- <option name="compatibility:include-filter" value="CtsNativeMediaSlTestCases" />
- <option name="compatibility:include-filter" value="CtsNativeMediaXaTestCases" />
+ <option name="compatibility:include-filter" value="CtsAccelerationTestCases" />
</configuration>