Merge "Remove Thread.sleep from device admin cts tests." into mnc-dev
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
index 36ac7a7..2a24f4d 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
@@ -32,6 +32,29 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <activity android:name=".SimpleIntentReceiverActivity" android:exported="true"/>
+
+ <activity-alias android:name=".BrowserActivity"
+ android:targetActivity=".SimpleIntentReceiverActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <data android:scheme="http"/>
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias android:name=".AppLinkActivity"
+ android:targetActivity=".SimpleIntentReceiverActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <data android:scheme="http" android:host="com.android.cts.intent.receiver"/>
+ </intent-filter>
+ </activity-alias>
+
</application>
</manifest>
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
new file mode 100644
index 0000000..23755df
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.intent.receiver;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import android.os.Bundle;
+
+/**
+ * An activity that receives an intent and returns immediately, indicating its own name and if it is
+ * running in a managed profile.
+ */
+public class SimpleIntentReceiverActivity extends Activity {
+ private static final String TAG = "SimpleIntentReceiverActivity";
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String className = getIntent().getComponent().getClassName();
+
+ // We try to check if we are in a managed profile or not.
+ // To do this, check if com.android.cts.managedprofile is the profile owner.
+ DevicePolicyManager dpm =
+ (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+ boolean inManagedProfile = dpm.isProfileOwnerApp("com.android.cts.managedprofile");
+
+ Log.i(TAG, "activity " + className + " started, is in managed profile: "
+ + inManagedProfile);
+ Intent result = new Intent();
+ result.putExtra("extra_receiver_class", className);
+ result.putExtra("extra_in_managed_profile", inManagedProfile);
+ setResult(Activity.RESULT_OK, result);
+ finish();
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
new file mode 100644
index 0000000..51ff362
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.intent.sender;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+public class AppLinkTest extends InstrumentationTestCase {
+
+ private static final String TAG = "AppLinkTest";
+
+ private Context mContext;
+ private IntentSenderActivity mActivity;
+
+ private static final String EXTRA_IN_MANAGED_PROFILE = "extra_in_managed_profile";
+ private static final String EXTRA_RECEIVER_CLASS = "extra_receiver_class";
+ private static final String APP_LINK_ACTIVITY
+ = "com.android.cts.intent.receiver.AppLinkActivity";
+ private static final String BROWSER_ACTIVITY
+ = "com.android.cts.intent.receiver.BrowserActivity";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getTargetContext();
+ mActivity = launchActivity(mContext.getPackageName(), IntentSenderActivity.class, null);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mActivity.finish();
+ super.tearDown();
+ }
+
+ public void testReceivedByAppLinkActivityInPrimary() throws Exception {
+ checkHttpIntentResult(APP_LINK_ACTIVITY, false);
+ }
+
+ public void testReceivedByAppLinkActivityInManaged() throws Exception {
+ checkHttpIntentResult(APP_LINK_ACTIVITY, true);
+ }
+
+ public void testReceivedByBrowserActivityInManaged() throws Exception {
+ checkHttpIntentResult(BROWSER_ACTIVITY, true);
+ }
+
+ public void testTwoReceivers() {
+ assertNumberOfReceivers(2);
+ }
+
+ public void testThreeReceivers() {
+ assertNumberOfReceivers(3);
+ }
+
+ // Should not be called if there are several possible receivers to the intent
+ // (see getHttpIntent)
+ private void checkHttpIntentResult(String receiverClassName, boolean inManagedProfile)
+ throws Exception {
+ PackageManager pm = mContext.getPackageManager();
+
+ Intent result = mActivity.getResult(getHttpIntent());
+ // If it is received in the other profile, we cannot check the class from the ResolveInfo
+ // returned by queryIntentActivities. So we rely on the receiver telling us its class.
+ assertEquals(receiverClassName, result.getStringExtra(EXTRA_RECEIVER_CLASS));
+ assertTrue(result.hasExtra(EXTRA_IN_MANAGED_PROFILE));
+ assertEquals(inManagedProfile, result.getBooleanExtra(EXTRA_IN_MANAGED_PROFILE, false));
+ }
+
+ private void assertNumberOfReceivers(int n) {
+ PackageManager pm = mContext.getPackageManager();
+ assertEquals(n, pm.queryIntentActivities(getHttpIntent(), /* flags = */ 0).size());
+ }
+
+ private Intent getHttpIntent() {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.addCategory(Intent.CATEGORY_BROWSABLE);
+ i.setData(Uri.parse("http://com.android.cts.intent.receiver"));
+ return i;
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
index fd421ac..eb64d47 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
@@ -66,8 +66,12 @@
}
public Intent getResult(Intent intent) throws Exception {
+ Log.d(TAG, "Sending intent " + intent);
startActivityForResult(intent, 42);
final Result result = mResult.poll(30, TimeUnit.SECONDS);
+ if (result != null) {
+ Log.d(TAG, "Result intent: " + result.data);
+ }
return (result != null) ? result.data : null;
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
index 7b3fba3..b31e74b 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -26,7 +26,8 @@
LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
-LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner compatibility-device-util_v2
+LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner compatibility-device-util_v2 \
+ ub-uiautomator
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 9bf7046..e03ebdc 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -52,12 +52,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <activity android:name=".ComponentDisablingActivity" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT"/>
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
+ <activity android:name=".ComponentDisablingActivity" android:exported="true">
</activity>
<activity android:name=".ManagedProfileActivity">
<intent-filter>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
index 2a54d97..49754d0 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
@@ -19,7 +19,8 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
-import android.test.AndroidTestCase;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
/**
* Base class for profile-owner based tests.
@@ -27,7 +28,7 @@
* This class handles making sure that the test is the profile owner and that it has an active admin
* registered, so that all tests may assume these are done.
*/
-public class BaseManagedProfileTest extends AndroidTestCase {
+public class BaseManagedProfileTest extends InstrumentationTestCase {
public static class BasicAdminReceiver extends DeviceAdminReceiver {
}
@@ -36,21 +37,23 @@
BasicAdminReceiver.class.getPackage().getName(), BasicAdminReceiver.class.getName());
protected DevicePolicyManager mDevicePolicyManager;
+ protected Context mContext;
@Override
protected void setUp() throws Exception {
super.setUp();
+ mContext = getInstrumentation().getContext();
- mDevicePolicyManager = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- assertNotNull(mDevicePolicyManager);
+ mDevicePolicyManager = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ assertNotNull(mDevicePolicyManager);
- // TODO: Only check the below if we are running as the profile user. If running under the
- // user owner, can we check that there is a profile and that the below holds for it? If we
- // don't want to do these checks every time we could get rid of this class altogether and
- // just have a single test case running under the profile user that do them.
- assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
- assertTrue(mDevicePolicyManager.isProfileOwnerApp(
- ADMIN_RECEIVER_COMPONENT.getPackageName()));
+ // TODO: Only check the below if we are running as the profile user. If running under the
+ // user owner, can we check that there is a profile and that the below holds for it? If we
+ // don't want to do these checks every time we could get rid of this class altogether and
+ // just have a single test case running under the profile user that do them.
+ assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
+ assertTrue(mDevicePolicyManager.isProfileOwnerApp(
+ ADMIN_RECEIVER_COMPONENT.getPackageName()));
}
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
index 21b2d36..6a63cea 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
@@ -20,9 +20,16 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
import android.os.UserManager;
import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.List;
/**
* The methods in this class are not really tests.
@@ -31,6 +38,8 @@
* device-side methods from the host.
*/
public class CrossProfileUtils extends AndroidTestCase {
+ private static final String TAG = "CrossProfileUtils";
+
private static final String ACTION_READ_FROM_URI = "com.android.cts.action.READ_FROM_URI";
private static final String ACTION_WRITE_TO_URI = "com.android.cts.action.WRITE_TO_URI";
@@ -86,4 +95,18 @@
dpm.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
}
+
+ // Disables all browsers in current user
+ public void testDisableAllBrowsers() {
+ PackageManager pm = (PackageManager) getContext().getPackageManager();
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+ Intent webIntent = new Intent(Intent.ACTION_VIEW);
+ webIntent.setData(Uri.parse("http://com.android.cts.intent.receiver"));
+ List<ResolveInfo> ris = pm.queryIntentActivities(webIntent, 0 /* no flags*/);
+ for (ResolveInfo ri : ris) {
+ Log.d(TAG, "Hiding " + ri.activityInfo.packageName);
+ dpm.setApplicationHidden(ADMIN_RECEIVER_COMPONENT, ri.activityInfo.packageName, true);
+ }
+ }
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java
index 2281e92..7ddf77f 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java
@@ -23,6 +23,12 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.UserManager;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiWatcher;
+import android.support.test.uiautomator.Until;
import android.util.Log;
import java.util.concurrent.ArrayBlockingQueue;
@@ -54,9 +60,18 @@
private static final String EXTRA_GRANT_STATE
= "com.android.cts.permission.extra.GRANT_STATE";
private static final int PERMISSION_ERROR = -2;
+ private static final BySelector CRASH_POPUP_BUTTON_SELECTOR = By
+ .clazz(android.widget.Button.class.getName())
+ .text("OK")
+ .pkg("android");
+ private static final BySelector CRASH_POPUP_TEXT_SELECTOR = By
+ .clazz(android.widget.TextView.class.getName())
+ .pkg("android");
+ private static final String CRASH_WATCHER_ID = "CRASH";
private PermissionBroadcastReceiver mReceiver;
private PackageManager mPackageManager;
+ private UiDevice mDevice;
@Override
protected void setUp() throws Exception {
@@ -69,11 +84,13 @@
mReceiver = new PermissionBroadcastReceiver();
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PERMISSION_RESULT));
mPackageManager = mContext.getPackageManager();
+ mDevice = UiDevice.getInstance(getInstrumentation());
}
@Override
protected void tearDown() throws Exception {
mContext.unregisterReceiver(mReceiver);
+ mDevice.removeWatcher(CRASH_WATCHER_ID);
super.tearDown();
}
@@ -144,6 +161,28 @@
assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
}
+ public void testPermissionPrompts() throws Exception {
+ // register a crash watcher
+ mDevice.registerWatcher(CRASH_WATCHER_ID, new UiWatcher() {
+ @Override
+ public boolean checkForCondition() {
+ UiObject2 button = mDevice.findObject(CRASH_POPUP_BUTTON_SELECTOR);
+ if (button != null) {
+ UiObject2 text = mDevice.findObject(CRASH_POPUP_TEXT_SELECTOR);
+ Log.d(TAG, "Removing an error dialog: " + text != null ? text.getText() : null);
+ button.click();
+ return true;
+ }
+ return false;
+ }
+ });
+ mDevice.runWatchers();
+
+ assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
+ assertPermissionRequest(PackageManager.PERMISSION_DENIED, "permission_deny_button");
+ assertPermissionRequest(PackageManager.PERMISSION_GRANTED, "permission_allow_button");
+ }
+
public void testPermissionUpdate_setDeniedState() throws Exception {
assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
@@ -192,6 +231,10 @@
}
private void assertPermissionRequest(int expected) throws Exception {
+ assertPermissionRequest(expected, null);
+ }
+
+ private void assertPermissionRequest(int expected, String buttonResource) throws Exception {
Intent launchIntent = new Intent();
launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
PERMISSIONS_ACTIVITY_NAME));
@@ -199,6 +242,7 @@
launchIntent.setAction(ACTION_REQUEST_PERMISSION);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
mContext.startActivity(launchIntent);
+ pressPermissionPromptButton(buttonResource);
assertEquals(expected, mReceiver.waitForBroadcast());
assertEquals(expected, mPackageManager.checkPermission(PERMISSION_NAME,
PERMISSION_APP_PACKAGE_NAME));
@@ -246,6 +290,20 @@
PackageManager.PERMISSION_GRANTED);
}
+ private void pressPermissionPromptButton(String resName) throws Exception {
+ if (resName == null) {
+ return;
+ }
+
+ BySelector selector = By
+ .clazz(android.widget.Button.class.getName())
+ .res("com.android.packageinstaller", resName);
+ mDevice.wait(Until.hasObject(selector), 5000);
+ UiObject2 button = mDevice.findObject(selector);
+ assertNotNull("Couldn't find button with resource id: " + resName, button);
+ button.click();
+ }
+
private class PermissionBroadcastReceiver extends BroadcastReceiver {
private BlockingQueue<Integer> mQueue = new ArrayBlockingQueue<Integer> (1);
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
index 76a9e44..9646e61 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
@@ -64,11 +64,4 @@
// Verify the profile is deleted
assertFalse(mUserManager.getUserProfiles().contains(currentUser));
}
-
- // Override this test inherited from base class, as it will trigger another round of setUp()
- // which would fail because the managed profile has been removed by this test.
- @Override
- @Ignore
- public void testAndroidTestCaseSetupProperly() {
- }
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 50987ef..52e1e75 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -55,6 +55,8 @@
private static final String FEATURE_CAMERA = "android.hardware.camera";
private static final String FEATURE_WIFI = "android.hardware.wifi";
+ private static final String ADD_RESTRICTION_COMMAND = "add-restriction";
+
private static final int USER_OWNER = 0;
// ID of the profile we'll create. This will always be a profile of USER_OWNER.
@@ -179,6 +181,49 @@
// TODO: Test with startActivity
}
+ public void testAppLinks() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ // Disable all pre-existing browsers in the managed profile so they don't interfere with
+ // intents resolution.
+ assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ "testDisableAllBrowsers", mUserId));
+ installApp(INTENT_RECEIVER_APK);
+ installApp(INTENT_SENDER_APK);
+
+ changeVerificationStatus(USER_OWNER, INTENT_RECEIVER_PKG, "ask");
+ changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "ask");
+ // We should have two receivers: IntentReceiverActivity and BrowserActivity in the
+ // managed profile
+ assertAppLinkResult("testTwoReceivers");
+
+ changeUserRestrictionForUser("allow_parent_profile_app_linking", ADD_RESTRICTION_COMMAND,
+ mUserId);
+ // Now we should also have one receiver in the primary user, so three receivers in total.
+ assertAppLinkResult("testThreeReceivers");
+
+ changeVerificationStatus(USER_OWNER, INTENT_RECEIVER_PKG, "never");
+ // The primary user one has been set to never: we should only have the managed profile ones.
+ assertAppLinkResult("testTwoReceivers");
+
+ changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "never");
+ // Now there's only the browser in the managed profile left
+ assertAppLinkResult("testReceivedByBrowserActivityInManaged");
+
+ changeVerificationStatus(USER_OWNER, INTENT_RECEIVER_PKG, "always");
+ changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "ask");
+ // We've set the receiver in the primary user to always: only this one should receive the
+ // intent.
+ assertAppLinkResult("testReceivedByAppLinkActivityInPrimary");
+
+ changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "always");
+ // We have one always in the primary user and one always in the managed profile: the managed
+ // profile one should have precedence.
+ assertAppLinkResult("testReceivedByAppLinkActivityInManaged");
+ }
+
+
public void testSettingsIntents() throws Exception {
if (!mHasFeature) {
return;
@@ -269,17 +314,16 @@
return;
}
String restriction = "no_debugging_features"; // UserManager.DISALLOW_DEBUGGING_FEATURES
- String command = "add-restriction";
String addRestrictionCommandOutput =
- changeUserRestrictionForUser(restriction, command, mUserId);
+ changeUserRestrictionForUser(restriction, ADD_RESTRICTION_COMMAND, mUserId);
assertTrue("Command was expected to succeed " + addRestrictionCommandOutput,
addRestrictionCommandOutput.contains("Status: ok"));
// This should now fail, as the shell is not available to start activities under a different
// user once the restriction is in place.
addRestrictionCommandOutput =
- changeUserRestrictionForUser(restriction, command, mUserId);
+ changeUserRestrictionForUser(restriction, ADD_RESTRICTION_COMMAND, mUserId);
assertTrue(
"Expected SecurityException when starting the activity "
+ addRestrictionCommandOutput,
@@ -532,6 +576,22 @@
"testPermissionMixedPolicies", mUserId));
}
+ public void testPermissionPrompts() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ try {
+ // unlock device and ensure that the screen stays on
+ getDevice().executeShellCommand("input keyevent 82");
+ getDevice().executeShellCommand("settings put global stay_on_while_plugged_in 2");
+ installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+ assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
+ "testPermissionPrompts", mUserId));
+ } finally {
+ getDevice().executeShellCommand("settings put global stay_on_while_plugged_in 0");
+ }
+ }
+
public void testPermissionAppUpdate() throws Exception {
if (!mHasFeature) {
return;
@@ -607,4 +667,16 @@
"Output for command " + adbCommand + ": " + commandOutput);
return commandOutput;
}
+
+ // status should be one of never, undefined, ask, always
+ private void changeVerificationStatus(int userId, String packageName, String status)
+ throws DeviceNotAvailableException {
+ String command = "pm set-app-link --user " + userId + " " + packageName + " " + status;
+ CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": "
+ + getDevice().executeShellCommand(command));
+ }
+
+ private void assertAppLinkResult(String methodName) throws DeviceNotAvailableException {
+ assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".AppLinkTest", methodName, mUserId));
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java b/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java
new file mode 100644
index 0000000..3403df3
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import java.math.BigInteger;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.interfaces.RSAKey;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+
+import android.security.keystore.KeyProperties;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+public class RSACipherTest extends AndroidTestCase {
+
+ private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
+
+ public void testNoPaddingEncryptionAndDecryptionSucceedsWithInputShorterThanModulus()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+ int modulusSizeBytes = (modulus.bitLength() + 7) / 8;
+
+ // 1-byte long input for which we know the output
+ byte[] input = new byte[] {1};
+ // Because of how RSA works, the output is 1 (left-padded with zero bytes).
+ byte[] expectedOutput = TestUtils.leftPadWithZeroBytes(input, modulusSizeBytes);
+
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ MoreAsserts.assertEquals(expectedOutput, cipher.doFinal(input));
+
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ MoreAsserts.assertEquals(expectedOutput, cipher.doFinal(input));
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingEncryptionSucceedsWithPlaintextOneSmallerThanModulus()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+ // Plaintext is one smaller than the modulus
+ byte[] plaintext =
+ TestUtils.getBigIntegerMagnitudeBytes(modulus.subtract(BigInteger.ONE));
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ MoreAsserts.assertEquals(plaintext, cipher.doFinal(ciphertext));
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingEncryptionFailsWithPlaintextEqualToModulus() throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT ,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+ // Plaintext is exactly the modulus
+ byte[] plaintext = TestUtils.getBigIntegerMagnitudeBytes(modulus);
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail("Unexpectedly produced ciphertext (" + ciphertext.length + " bytes): "
+ + HexEncoding.encode(ciphertext));
+ } catch (BadPaddingException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingEncryptionFailsWithPlaintextOneLargerThanModulus() throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+ // Plaintext is one larger than the modulus
+ byte[] plaintext =
+ TestUtils.getBigIntegerMagnitudeBytes(modulus.add(BigInteger.ONE));
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail("Unexpectedly produced ciphertext (" + ciphertext.length + " bytes): "
+ + HexEncoding.encode(ciphertext));
+ } catch (BadPaddingException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingEncryptionFailsWithPlaintextOneByteLongerThanModulus()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+ // Plaintext is one byte longer than the modulus. The message is filled with zeros
+ // (thus being 0 if treated as a BigInteger). This is on purpose, to check that the
+ // Cipher implementation rejects such long message without comparing it to the value
+ // of the modulus.
+ byte[] plaintext = new byte[((modulus.bitLength() + 7) / 8) + 1];
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ try {
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ fail("Unexpectedly produced ciphertext (" + ciphertext.length + " bytes): "
+ + HexEncoding.encode(ciphertext));
+ } catch (IllegalBlockSizeException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingDecryptionFailsWithCiphertextOneByteLongerThanModulus()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_DECRYPT,
+ false))) {
+ try {
+ PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+ BigInteger modulus = ((RSAKey) privateKey).getModulus();
+
+ // Ciphertext is one byte longer than the modulus. The message is filled with zeros
+ // (thus being 0 if treated as a BigInteger). This is on purpose, to check that the
+ // Cipher implementation rejects such long message without comparing it to the value
+ // of the modulus.
+ byte[] ciphertext = new byte[((modulus.bitLength() + 7) / 8) + 1];
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ try {
+ byte[] plaintext = cipher.doFinal(ciphertext);
+ fail("Unexpectedly produced plaintext (" + ciphertext.length + " bytes): "
+ + HexEncoding.encode(plaintext));
+ } catch (IllegalBlockSizeException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+
+ public void testNoPaddingWithZeroMessage() throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+
+ for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+ TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+ "RSA/ECB/NoPadding",
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+ false))) {
+ try {
+ PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+ PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+
+ byte[] plaintext = EmptyArray.BYTE;
+ Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ byte[] ciphertext = cipher.doFinal(plaintext);
+ // Ciphertext should be all zero bytes
+ byte[] expectedCiphertext = new byte[(TestUtils.getKeySizeBits(publicKey) + 7) / 8];
+ MoreAsserts.assertEquals(expectedCiphertext, ciphertext);
+
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ // Decrypted plaintext should also be all zero bytes
+ byte[] expectedPlaintext = new byte[expectedCiphertext.length];
+ MoreAsserts.assertEquals(expectedPlaintext, cipher.doFinal(ciphertext));
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for key " + key.getAlias(), e);
+ }
+ }
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
index 1c4f6ad..b1ba453 100644
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
@@ -27,6 +27,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
@@ -890,4 +891,15 @@
throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
}
+
+ static byte[] getBigIntegerMagnitudeBytes(BigInteger value) {
+ return removeLeadingZeroByteIfPresent(value.toByteArray());
+ }
+
+ private static byte[] removeLeadingZeroByteIfPresent(byte[] value) {
+ if ((value.length < 1) || (value[0] != 0)) {
+ return value;
+ }
+ return TestUtils.subarray(value, 1, value.length - 1);
+ }
}