1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.matchflags.cts;
18 
19 import static org.junit.Assert.fail;
20 import static org.junit.Assume.assumeFalse;
21 
22 import android.content.ActivityNotFoundException;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.net.Uri;
26 
27 import androidx.test.ext.junit.runners.AndroidJUnit4;
28 import androidx.test.platform.app.InstrumentationRegistry;
29 
30 import com.android.compatibility.common.util.FeatureUtil;
31 import com.android.compatibility.common.util.ShellUtils;
32 
33 import org.junit.After;
34 import org.junit.Before;
35 import org.junit.Rule;
36 import org.junit.Test;
37 import org.junit.rules.TestName;
38 import org.junit.runner.RunWith;
39 
40 @RunWith(AndroidJUnit4.class)
41 public class MatchFlagTests {
42 
43     private static final String ONLY_BROWSER_URI =
44             "https://nohandler-02xgpcssu1v7xvpek0skc905glnyu7ihjtza3eufox0mauqyri.com";
45     private static final String UNIQUE_URI =
46             "https://unique-5gle2bs6woovjn8xabwyb3js01xl0ducci3gd3fpe622h48lyg.com";
47 
48     private static final String SHARED_PKG_NAME = "android.matchflags.app.shared";
49     private static final String UNIQUE_AND_SHARED_PKG_NAME =
50             "android.matchflags.app.uniqueandshared";
51 
52     @Rule
53     public TestName name = new TestName();
54 
55     @Before
56     @After
removeApprovals()57     public void removeApprovals() {
58         setDomainUserSelectionApproval(false);
59     }
60 
61     @Test
startNoBrowserIntentWithNoMatchingApps()62     public void startNoBrowserIntentWithNoMatchingApps() throws Exception {
63         assumeFalse("Skipping test for watch",
64                 InstrumentationRegistry.getInstrumentation().getTargetContext()
65                         .getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
66 
67         Intent onlyBrowserIntent = new Intent(Intent.ACTION_VIEW)
68                 .addCategory(Intent.CATEGORY_BROWSABLE)
69                 .setData(Uri.parse(ONLY_BROWSER_URI));
70 
71         if (isBrowserPresent(true)) {
72             startActivity(onlyBrowserIntent);
73         } else {
74             try {
75                 startActivity(onlyBrowserIntent);
76                 fail("Device without browser should not launch browser only intent");
77             } catch (ActivityNotFoundException e) {
78                 // hooray
79             }
80         }
81 
82         Intent noBrowserWithBrowserOnlyIntent = new Intent(onlyBrowserIntent)
83                 .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
84 
85         try {
86             startActivity(noBrowserWithBrowserOnlyIntent);
87             fail("Should not have started a browser-only intent with NON_BROWSER flag set");
88         } catch (ActivityNotFoundException e) {
89             // hooray!
90         }
91     }
92 
93     @Test
startRequireDefaultWithNoDefault()94     public void startRequireDefaultWithNoDefault() throws Exception {
95         Intent sharedIntent = new Intent("android.matchflags.app.SHARED_ACTION");
96 
97         startActivity(sharedIntent);
98 
99         Intent sharedIntentRequireDefault = new Intent(sharedIntent)
100                 .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT);
101 
102         try {
103             startActivity(sharedIntentRequireDefault);
104             fail("Should have started intent with no default when default required");
105         } catch (ActivityNotFoundException e) {
106             // hooray!
107         }
108     }
109 
110     @Test
startRequireDefaultWithSingleMatch()111     public void startRequireDefaultWithSingleMatch() throws Exception {
112         Intent uniqueIntent = new Intent("android.matchflags.app.UNIQUE_ACTION")
113                 .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT);
114         startActivity(uniqueIntent);
115     }
116 
117     @Test
startNoBrowserRequireDefault()118     public void startNoBrowserRequireDefault() throws Exception {
119         setDomainUserSelectionApproval(true);
120         startNoBrowserRequireDefaultInternal(true);
121     }
122 
123     @Test
startNoBrowserRequireDefaultUnapproved()124     public void startNoBrowserRequireDefaultUnapproved() throws Exception {
125         assumeFalse("Skipping test for watch", FeatureUtil.isWatch());
126         setDomainUserSelectionApproval(false);
127         startNoBrowserRequireDefaultInternal(false);
128     }
129 
startNoBrowserRequireDefaultInternal(boolean isDomainApproved)130     private void startNoBrowserRequireDefaultInternal(boolean isDomainApproved) {
131         Intent uniqueUriIntent = new Intent(Intent.ACTION_VIEW)
132                 .addCategory(Intent.CATEGORY_BROWSABLE)
133                 .setData(Uri.parse(UNIQUE_URI));
134 
135         startActivity(uniqueUriIntent);
136 
137         Intent uniqueUriIntentNoBrowserRequireDefault = new Intent(uniqueUriIntent)
138                 .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER)
139                 .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT);
140 
141         if (isBrowserPresent(false)) {
142             // with non-browser, we'd expect the resolver
143             // with require default, we'll get activity not found
144             try {
145                 startActivity(uniqueUriIntentNoBrowserRequireDefault);
146                 if (!isDomainApproved) {
147                     fail("Should fail to launch when started with non-browser and require default"
148                             + " when browser present");
149                 }
150             } catch (ActivityNotFoundException e) {
151                 // hooray!
152                 if (isDomainApproved) {
153                     // Domain approval should force only the test Activity to be returned, which
154                     // means it should pass the above flags and launch.
155                     fail("Should succeed launch when started with non-browser and require default"
156                             + " when browser present");
157                 }
158             }
159         } else {
160             // with non-browser, but no browser present, we'd get a single result
161             // with require default, we'll resolve to that single result
162             try {
163                 startActivity(uniqueUriIntentNoBrowserRequireDefault);
164                 if (!isDomainApproved) {
165                     fail("Should fail to launch when started with non-browser and require default"
166                             + " when browser not present");
167                 }
168             } catch (ActivityNotFoundException e) {
169                 if (isDomainApproved) {
170                     fail("Should succeed launch when started with non-browser and require default"
171                             + " when browser not present");
172                 }
173             }
174         }
175     }
176 
isBrowserPresent(boolean includeFallback)177     private static boolean isBrowserPresent(boolean includeFallback) {
178         return InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager()
179                 .queryIntentActivities(new Intent(Intent.ACTION_VIEW).addCategory(
180                         Intent.CATEGORY_BROWSABLE).setData(Uri.parse(ONLY_BROWSER_URI)),
181                         0 /* flags */)
182                 .stream().anyMatch(resolveInfo ->
183                         resolveInfo.handleAllWebDataURI
184                         && (includeFallback || resolveInfo.priority >= 0));
185     }
186 
startActivity(Intent onlyBrowserIntent)187     private static void startActivity(Intent onlyBrowserIntent) {
188         InstrumentationRegistry.getInstrumentation().getContext().startActivity(
189                 onlyBrowserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
190     }
191 
setDomainUserSelectionApproval(boolean approved)192     private static void setDomainUserSelectionApproval(boolean approved) {
193         String template = "pm set-app-links-user-selection --package %s --user all %b all";
194         ShellUtils.runShellCommand(template, SHARED_PKG_NAME, approved);
195         ShellUtils.runShellCommand(template, UNIQUE_AND_SHARED_PKG_NAME, approved);
196     }
197 }
198