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