Cts tests for cross-profile app linking.
BUG:22259892
Change-Id: I36ff381094bdb8be88e46462e330a4426470467a
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/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/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/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 50987ef..481d645 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,
@@ -607,4 +651,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));
+ }
}