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.dynamicmime.testapp.preferred;
18 
19 import static android.dynamicmime.common.Constants.APPLICATION_LABEL_TEST_APP;
20 import static android.dynamicmime.common.Constants.ACTIVITY_BOTH;
21 import static android.dynamicmime.common.Constants.ACTIVITY_FIRST;
22 import static android.dynamicmime.common.Constants.APK_PREFERRED_APP;
23 import static android.dynamicmime.common.Constants.GROUP_FIRST;
24 import static android.dynamicmime.common.Constants.GROUP_SECOND;
25 import static android.dynamicmime.common.Constants.GROUP_THIRD;
26 import static android.dynamicmime.common.Constants.MIME_IMAGE_PNG;
27 import static android.dynamicmime.common.Constants.MIME_TEXT_PLAIN;
28 import static android.dynamicmime.common.Constants.PACKAGE_PREFERRED_APP;
29 
30 import static org.junit.Assert.assertNotNull;
31 import static org.junit.Assert.assertNull;
32 import static org.junit.Assume.assumeTrue;
33 
34 import android.content.Intent;
35 import android.content.res.Resources;
36 import android.dynamicmime.testapp.BaseDynamicMimeTest;
37 import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
38 import android.dynamicmime.testapp.commands.MimeGroupCommands;
39 import android.dynamicmime.testapp.util.Utils;
40 import android.os.SystemClock;
41 
42 import androidx.test.ext.junit.runners.AndroidJUnit4;
43 import androidx.test.uiautomator.By;
44 import androidx.test.uiautomator.BySelector;
45 import androidx.test.uiautomator.Direction;
46 import androidx.test.uiautomator.UiDevice;
47 import androidx.test.uiautomator.UiObject2;
48 import androidx.test.uiautomator.UiObjectNotFoundException;
49 import androidx.test.uiautomator.UiScrollable;
50 import androidx.test.uiautomator.UiSelector;
51 import androidx.test.uiautomator.Until;
52 
53 import org.junit.After;
54 import org.junit.Before;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 
58 import java.util.concurrent.TimeUnit;
59 import java.util.regex.Pattern;
60 
61 @RunWith(AndroidJUnit4.class)
62 public class PreferredActivitiesTest extends BaseDynamicMimeTest {
63     private static final String ACTION = "android.dynamicmime.preferred.TEST_ACTION";
64 
65     private static final String NAV_BAR_INTERACTION_MODE_RES_NAME = "config_navBarInteractionMode";
66     private static final int NAV_BAR_INTERACTION_MODE_GESTURAL = 2;
67 
68     private static final String BUTTON_ALWAYS_RES_ID = ".*:id/button_always.*";
69     private static final BySelector BUTTON_ALWAYS = By.res(Pattern.compile(BUTTON_ALWAYS_RES_ID));
70     private static final UiSelector BUTTON_ALWAYS_UI_SELECTOR =
71             new UiSelector().resourceId(BUTTON_ALWAYS_RES_ID);
72     private static final BySelector RESOLVER_DIALOG = By.res(Pattern.compile(".*:id/contentPanel.*"));
73 
74     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(60L);
75 
76     private static final String FEATURE_WEARABLE = "android.hardware.type.watch";
77     private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
78 
79     private TestStrategy mTest;
80 
PreferredActivitiesTest()81     public PreferredActivitiesTest() {
82         super(MimeGroupCommands.preferredApp(context()), MimeGroupAssertions.notUsed());
83         assumeNavigationMode();
84     }
85 
assumeNavigationMode()86     private void assumeNavigationMode() {
87         Resources res = context().getResources();
88         int navModeResId = res.getIdentifier(NAV_BAR_INTERACTION_MODE_RES_NAME, "integer",
89             "android");
90         int navMode = res.getInteger(navModeResId);
91 
92         assumeTrue("Non-gesture navigation mode required",
93             navMode != NAV_BAR_INTERACTION_MODE_GESTURAL);
94     }
95 
96     @Before
setUp()97     public void setUp() {
98         Utils.installApk(APK_PREFERRED_APP);
99     }
100 
101     @After
tearDown()102     public void tearDown() {
103         super.tearDown();
104         Utils.uninstallApp(PACKAGE_PREFERRED_APP);
105     }
106 
107     @Test
testRemoveFromGroup()108     public void testRemoveFromGroup() {
109         setStrategyAndRun(new TestStrategy() {
110             @Override
111             public void prepareMimeGroups() {
112                 addMimeTypeToGroup(GROUP_FIRST, mimeType());
113             }
114 
115             @Override
116             public void changeMimeGroups() {
117                 removeMimeTypeFromGroup(GROUP_FIRST, mimeType());
118             }
119 
120             @Override
121             public String preferredActivity() {
122                 return ACTIVITY_FIRST;
123             }
124 
125             @Override
126             public String preferredApplicationLabel() {
127                 return APPLICATION_LABEL_TEST_APP;
128             }
129         });
130     }
131 
132     @Test
testAddToGroup()133     public void testAddToGroup() {
134         setStrategyAndRun(new TestStrategy() {
135             @Override
136             public void prepareMimeGroups() {
137                 addMimeTypeToGroup(GROUP_FIRST, mimeType());
138             }
139 
140             @Override
141             public void changeMimeGroups() {
142                 addMimeTypeToGroup(GROUP_FIRST, MIME_IMAGE_PNG);
143             }
144 
145             @Override
146             public void revertMimeGroupsChange() {
147                 removeMimeTypeFromGroup(GROUP_FIRST, MIME_IMAGE_PNG);
148             }
149 
150             @Override
151             public String preferredActivity() {
152                 return ACTIVITY_FIRST;
153             }
154 
155             @Override
156             public String preferredApplicationLabel() {
157                 return APPLICATION_LABEL_TEST_APP;
158             }
159         });
160     }
161 
162     @Test
testClearGroup()163     public void testClearGroup() {
164         setStrategyAndRun(new TestStrategy() {
165             @Override
166             public void prepareMimeGroups() {
167                 addMimeTypeToGroup(GROUP_FIRST, mimeType());
168             }
169 
170             @Override
171             public void changeMimeGroups() {
172                 clearMimeGroup(GROUP_FIRST);
173             }
174 
175             @Override
176             public String preferredActivity() {
177                 return ACTIVITY_FIRST;
178             }
179 
180             @Override
181             public String preferredApplicationLabel() {
182                 return APPLICATION_LABEL_TEST_APP;
183             }
184         });
185     }
186 
187     @Test
testModifyGroupWithoutActualGroupChanges()188     public void testModifyGroupWithoutActualGroupChanges() {
189         setStrategyAndRun(new TestStrategy() {
190             @Override
191             public void prepareMimeGroups() {
192                 addMimeTypeToGroup(GROUP_FIRST, mimeType());
193             }
194 
195             @Override
196             public void changeMimeGroups() {
197                 addMimeTypeToGroup(GROUP_FIRST, mimeType());
198             }
199 
200             @Override
201             public String preferredActivity() {
202                 return ACTIVITY_FIRST;
203             }
204 
205             @Override
206             public String preferredApplicationLabel() {
207                 return APPLICATION_LABEL_TEST_APP;
208             }
209 
210             @Override
211             public boolean isActivityPreferredAfterRevert() {
212                 return true;
213             }
214 
215             @Override
216             public boolean isActivityPreferredAfterChange() {
217                 return true;
218             }
219         });
220     }
221 
222     @Test
testModifyGroupWithoutActualIntentFilterChanges()223     public void testModifyGroupWithoutActualIntentFilterChanges() {
224         setStrategyAndRun(new TestStrategy() {
225             @Override
226             public void prepareMimeGroups() {
227                 addMimeTypeToGroup(GROUP_THIRD, mimeType());
228                 addMimeTypeToGroup(GROUP_SECOND, mimeType());
229             }
230 
231             @Override
232             public void changeMimeGroups() {
233                 removeMimeTypeFromGroup(GROUP_THIRD, mimeType());
234             }
235 
236             @Override
237             public void revertMimeGroupsChange() {
238                 addMimeTypeToGroup(GROUP_THIRD, mimeType());
239             }
240 
241             @Override
242             public String preferredActivity() {
243                 return ACTIVITY_BOTH;
244             }
245 
246             @Override
247             public String preferredApplicationLabel() {
248                 return APPLICATION_LABEL_TEST_APP;
249             }
250 
251             @Override
252             public boolean isActivityPreferredAfterRevert() {
253                 return true;
254             }
255 
256             @Override
257             public boolean isActivityPreferredAfterChange() {
258                 return true;
259             }
260         });
261     }
262 
setStrategyAndRun(TestStrategy test)263     private void setStrategyAndRun(TestStrategy test) {
264         mTest = test;
265         runTest();
266     }
267 
runTest()268     private void runTest() {
269         mTest.prepareMimeGroups();
270         setPreferredActivity();
271 
272         mTest.changeMimeGroups();
273         checkPreferredActivityAfterChange();
274 
275         mTest.revertMimeGroupsChange();
276         checkPreferredActivityAfterRevert();
277 
278         getUiDevice().pressHome();
279     }
280 
setPreferredActivity()281     private void setPreferredActivity() {
282         triggerResolutionDialog();
283 
284         verifyDialogIsShown(true);
285 
286         chooseActivity("TestApp" + mTest.preferredActivity());
287     }
288 
checkPreferredActivityAfterChange()289     private void checkPreferredActivityAfterChange() {
290         checkPreferredActivity(mTest.isActivityPreferredAfterChange());
291     }
292 
checkPreferredActivityAfterRevert()293     private void checkPreferredActivityAfterRevert() {
294         checkPreferredActivity(mTest.isActivityPreferredAfterRevert());
295     }
296 
checkPreferredActivity(boolean hasPreferredActivity)297     private void checkPreferredActivity(boolean hasPreferredActivity) {
298         triggerResolutionDialog();
299         verifyResolutionDialog(hasPreferredActivity);
300     }
301 
triggerResolutionDialog()302     private void triggerResolutionDialog() {
303         getUiDevice().pressHome();
304         sendIntent(mTest.mimeType());
305     }
306 
verifyResolutionDialog(boolean shouldLaunchActivity)307     private void verifyResolutionDialog(boolean shouldLaunchActivity) {
308         verifyDialogIsShown(!shouldLaunchActivity);
309         getUiDevice().pressBack();
310     }
311 
verifyDialogIsShown(boolean shouldBeShown)312     private void verifyDialogIsShown(boolean shouldBeShown) {
313         if (Utils.hasFeature(FEATURE_WEARABLE)) {
314             scrollToSelector(BUTTON_ALWAYS_UI_SELECTOR);
315         }
316         UiObject2 buttonAlways = getUiDevice().wait(Until.findObject(BUTTON_ALWAYS), TIMEOUT);
317 
318         if (shouldBeShown) {
319             assertNotNull(buttonAlways);
320         } else {
321             assertNull(buttonAlways);
322         }
323     }
324 
chooseActivity(String label)325     private void chooseActivity(String label) {
326         UiObject2 mUIObject = findObjectInDialog(label);
327         // Get UIObject based on application label, if chooser dialog displays application first
328         if (mUIObject == null) {
329             mUIObject = findObjectInDialog(mTest.preferredApplicationLabel());
330             mUIObject.click();
331             mUIObject = findObjectInDialog(label);
332         }
333         mUIObject.click();
334         chooseUseAlways();
335 
336         getUiDevice().pressBack();
337     }
338 
findObjectInDialog(String label)339     private UiObject2 findObjectInDialog(String label) {
340         if (!Utils.hasFeature(FEATURE_WEARABLE) && !Utils.hasFeature(FEATURE_AUTOMOTIVE)) {
341             getUiDevice()
342                 .wait(Until.findObject(RESOLVER_DIALOG), TIMEOUT)
343                 .swipe(Direction.UP, 1f);
344         } else {
345             scrollToSelector(new UiSelector().text(label));
346         }
347         return getUiDevice().findObject(By.text(label));
348     }
349 
chooseUseAlways()350     private void chooseUseAlways() {
351         if (Utils.hasFeature(FEATURE_WEARABLE)) {
352             scrollToSelector(BUTTON_ALWAYS_UI_SELECTOR);
353         }
354         getUiDevice()
355                 .wait(Until.findObject(BUTTON_ALWAYS), TIMEOUT)
356                 .click();
357     }
358 
scrollToSelector(UiSelector selector)359     private void scrollToSelector(UiSelector selector) {
360         try {
361             int resId = Resources.getSystem().getIdentifier(
362                     "config_customResolverActivity", "string", "android");
363             String customResolverActivity = context().getString(resId);
364             String customResolverPackageName;
365             if (customResolverActivity.isEmpty()) {
366                 // If custom resolver is not in use, it'll be using the Android default
367                 customResolverPackageName = "android";
368             } else {
369                 customResolverPackageName = customResolverActivity.split("/")[0];
370             }
371 
372             UiSelector scrollableSelector =
373                     new UiSelector()
374                             .scrollable(true)
375                             .packageName(customResolverPackageName);
376             UiScrollable scrollable = new UiScrollable(scrollableSelector);
377             scrollable.waitForExists(TIMEOUT);
378             if (scrollable.exists()) {
379                 scrollable.scrollToBeginning(Integer.MAX_VALUE);
380                 scrollable.scrollIntoView(selector);
381                 SystemClock.sleep(1000L);
382             }
383         } catch (UiObjectNotFoundException ignore) {
384             throw new AssertionError("Scrollable view was lost.");
385         }
386     }
387 
388     private interface TestStrategy {
prepareMimeGroups()389         void prepareMimeGroups();
390 
changeMimeGroups()391         void changeMimeGroups();
392 
revertMimeGroupsChange()393         default void revertMimeGroupsChange() {
394             prepareMimeGroups();
395         }
396 
mimeType()397         default String mimeType() {
398             return MIME_TEXT_PLAIN;
399         }
400 
preferredActivity()401         String preferredActivity();
402 
preferredApplicationLabel()403         String preferredApplicationLabel();
404 
isActivityPreferredAfterChange()405         default boolean isActivityPreferredAfterChange() {
406             return false;
407         }
408 
isActivityPreferredAfterRevert()409         default boolean isActivityPreferredAfterRevert() {
410             return false;
411         }
412     }
413 
getUiDevice()414     private static UiDevice getUiDevice() {
415         return UiDevice.getInstance(instrumentation());
416     }
417 
sendIntent(String mimeType)418     private static void sendIntent(String mimeType) {
419         Intent sendIntent = new Intent();
420         sendIntent.setAction(ACTION);
421         sendIntent.setType(mimeType);
422         sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
423         targetContext().startActivity(sendIntent, null);
424     }
425 }
426