1 /* 2 * Copyright (C) 2016 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 com.android.functional.applinktests; 18 19 import android.app.UiAutomation; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.ParcelFileDescriptor; 23 import android.support.test.uiautomator.By; 24 import android.support.test.uiautomator.UiDevice; 25 import android.support.test.uiautomator.UiObject2; 26 import android.support.test.uiautomator.Until; 27 import android.test.InstrumentationTestCase; 28 import android.util.Log; 29 import android.view.accessibility.AccessibilityWindowInfo; 30 31 import java.io.BufferedReader; 32 import java.io.FileInputStream; 33 import java.io.IOException; 34 import java.io.InputStreamReader; 35 import java.util.ArrayList; 36 import java.util.List; 37 38 public class AppLinkTests extends InstrumentationTestCase { 39 public final String TEST_TAG = "AppLinkFunctionalTest"; 40 public final String TEST_PKG_NAME = "com.android.applinktestapp"; 41 public final String TEST_APP_NAME = "AppLinkTestApp"; 42 public final String YOUTUBE_PKG_NAME = "com.google.android.youtube"; 43 public final String HTTP_SCHEME = "http"; 44 public final String TEST_HOST = "test.com"; 45 public final int TIMEOUT = 1000; 46 private UiDevice mDevice = null; 47 private Context mContext = null; 48 private UiAutomation mUiAutomation = null; 49 50 @Override setUp()51 protected void setUp() throws Exception { 52 super.setUp(); 53 mDevice = UiDevice.getInstance(getInstrumentation()); 54 mContext = getInstrumentation().getContext(); 55 mUiAutomation = getInstrumentation().getUiAutomation(); 56 mDevice.setOrientationNatural(); 57 } 58 59 // Ensures that default app link setting set to 'undefined' for 3P apps testDefaultAppLinkSettting()60 public void testDefaultAppLinkSettting() throws InterruptedException { 61 String out = getAppLink(TEST_PKG_NAME); 62 assertTrue("Default app link not set to 'undefined' mode", "undefined".equals(out)); 63 openLink(HTTP_SCHEME, TEST_HOST); 64 ensureDisambigPresent(); 65 } 66 67 // User sets an app to open for a link 'Just Once' and disambig shows up next time too 68 // Once user set to 'always' disambig never shows up testUserSetToJustOnceAndAlways()69 public void testUserSetToJustOnceAndAlways() throws InterruptedException { 70 openLink(HTTP_SCHEME, TEST_HOST); 71 ensureDisambigPresent(); 72 mDevice.wait(Until.findObject(By.text("AppLinkTestApp")), TIMEOUT).click(); 73 mDevice.wait(Until.findObject(By.res("android:id/button_once")), TIMEOUT).click(); 74 Thread.sleep(TIMEOUT); 75 verifyForegroundAppPackage(TEST_PKG_NAME); 76 openLink(HTTP_SCHEME, TEST_HOST); 77 assertTrue("Target app isn't the default choice", 78 mDevice.wait(Until.hasObject(By.text("Open with AppLinkTestApp")), TIMEOUT)); 79 mDevice.wait(Until.findObject(By.res("android:id/button_once")), TIMEOUT) 80 .clickAndWait(Until.newWindow(), TIMEOUT); 81 Thread.sleep(TIMEOUT); 82 verifyForegroundAppPackage(TEST_PKG_NAME); 83 mDevice.pressHome(); 84 // Ensure it doesn't change on second attempt 85 openLink(HTTP_SCHEME, TEST_HOST); 86 // Ensure disambig is present 87 mDevice.wait(Until.findObject(By.res("android:id/button_always")), TIMEOUT).click(); 88 mDevice.pressHome(); 89 // User chose to set to always and intent is opened in target direct 90 openLink(HTTP_SCHEME, TEST_HOST); 91 verifyForegroundAppPackage(TEST_PKG_NAME); 92 } 93 94 // Ensure verified app always open even candidate but unverified app set to 'always' testVerifiedAppOpenWhenNotVerifiedSetToAlways()95 public void testVerifiedAppOpenWhenNotVerifiedSetToAlways() throws InterruptedException { 96 setAppLink(TEST_PKG_NAME, "always"); 97 setAppLink(YOUTUBE_PKG_NAME, "always"); 98 Thread.sleep(TIMEOUT); 99 openLink(HTTP_SCHEME, "youtube.com"); 100 verifyForegroundAppPackage(YOUTUBE_PKG_NAME); 101 } 102 103 // Ensure verified app always open even one candidate but unverified app set to 'ask' testVerifiedAppOpenWhenUnverifiedSetToAsk()104 public void testVerifiedAppOpenWhenUnverifiedSetToAsk() throws InterruptedException { 105 setAppLink(TEST_PKG_NAME, "ask"); 106 setAppLink(YOUTUBE_PKG_NAME, "always"); 107 String out = getAppLink(YOUTUBE_PKG_NAME); 108 openLink(HTTP_SCHEME, "youtube.com"); 109 verifyForegroundAppPackage(YOUTUBE_PKG_NAME); 110 } 111 112 // Ensure disambig is shown if verified app set to 'never' and unverified app set to 'ask' testUserChangeVerifiedLinkHandler()113 public void testUserChangeVerifiedLinkHandler() throws InterruptedException { 114 setAppLink(TEST_PKG_NAME, "ask"); 115 setAppLink(YOUTUBE_PKG_NAME, "never"); 116 Thread.sleep(TIMEOUT); 117 openLink(HTTP_SCHEME, "youtube.com"); 118 ensureDisambigPresent(); 119 setAppLink(YOUTUBE_PKG_NAME, "always"); 120 Thread.sleep(TIMEOUT); 121 openLink(HTTP_SCHEME, "youtube.com"); 122 verifyForegroundAppPackage(YOUTUBE_PKG_NAME); 123 } 124 125 // Ensure unverified app always open when unverified app set to always but verified app set to 126 // never testTestAppSetToAlwaysVerifiedSetToNever()127 public void testTestAppSetToAlwaysVerifiedSetToNever() throws InterruptedException { 128 setAppLink(TEST_PKG_NAME, "always"); 129 setAppLink(YOUTUBE_PKG_NAME, "never"); 130 Thread.sleep(TIMEOUT); 131 openLink(HTTP_SCHEME, "youtube.com"); 132 verifyForegroundAppPackage(TEST_PKG_NAME); 133 } 134 135 // Test user can modify 'App Link Settings' testSettingsChangeUI()136 public void testSettingsChangeUI() throws InterruptedException { 137 Intent intent_as = new Intent( 138 android.provider.Settings.ACTION_APPLICATION_SETTINGS); 139 mContext.startActivity(intent_as); 140 Thread.sleep(TIMEOUT * 5); 141 mDevice.wait(Until.findObject(By.res("com.android.settings:id/advanced")), TIMEOUT) 142 .clickAndWait(Until.newWindow(), TIMEOUT); 143 mDevice.wait(Until.findObject(By.text("Opening links")), TIMEOUT) 144 .clickAndWait(Until.newWindow(), TIMEOUT); 145 mDevice.wait(Until.findObject(By.text("AppLinkTestApp")), TIMEOUT) 146 .clickAndWait(Until.newWindow(), TIMEOUT); 147 mDevice.wait(Until.findObject(By.text("Open supported links")), TIMEOUT) 148 .clickAndWait(Until.newWindow(), TIMEOUT); 149 mDevice.wait(Until.findObject(By.text("Open in this app")), TIMEOUT) 150 .clickAndWait(Until.newWindow(), TIMEOUT); 151 String out = getAppLink(TEST_PKG_NAME); 152 Thread.sleep(TIMEOUT); 153 assertTrue(String.format("Default app link not set to 'always ask' rather set to %s", out), 154 "always".equals(out)); 155 mDevice.wait(Until.findObject(By.text("Open supported links")), TIMEOUT) 156 .clickAndWait(Until.newWindow(), TIMEOUT); 157 mDevice.wait(Until.findObject(By.text("Don’t open in this app")), TIMEOUT) 158 .clickAndWait(Until.newWindow(), TIMEOUT); 159 out = getAppLink(TEST_PKG_NAME); 160 Thread.sleep(TIMEOUT); 161 assertTrue(String.format("Default app link not set to 'never' rather set to %s", out), 162 "never".equals(out)); 163 mDevice.wait(Until.findObject(By.text("Open supported links")), TIMEOUT) 164 .clickAndWait(Until.newWindow(), TIMEOUT); 165 mDevice.wait(Until.findObject(By.text("Ask every time")), TIMEOUT) 166 .clickAndWait(Until.newWindow(), TIMEOUT); 167 out = getAppLink(TEST_PKG_NAME); 168 Thread.sleep(TIMEOUT); 169 assertTrue(String.format("Default app link not set to 'always ask' rather set to %s", out), 170 "always ask".equals(out)); 171 } 172 173 // Ensure system apps that claim to open always for set to always testSysappAppLinkSettings()174 public void testSysappAppLinkSettings() { 175 // List of system app that are set to 'Always' for certain urls 176 List<String> alwaysOpenApps = new ArrayList<String>(); 177 alwaysOpenApps.add("com.android.vending"); // Playstore 178 alwaysOpenApps.add("com.google.android.apps.docs"); // Drive 179 alwaysOpenApps.add("com.google.android.apps.maps"); // Map 180 alwaysOpenApps.add("com.google.android.calendar"); // Calendar 181 alwaysOpenApps.add("com.google.android.music"); // PlayMusic 182 alwaysOpenApps.add("com.google.android.youtube"); // YouTube 183 for (String alwaysOpenApp : alwaysOpenApps) { 184 String out = getAppLink(alwaysOpenApp); 185 assertTrue(String.format("App link for %s should be set to 'Always'", alwaysOpenApp), 186 "always".equalsIgnoreCase(out)); 187 } 188 } 189 190 @Override tearDown()191 protected void tearDown() throws Exception { 192 executeShellCommand("pm clear " + TEST_PKG_NAME); 193 executeShellCommand("pm clear " + YOUTUBE_PKG_NAME); 194 executeShellCommand("pm set-app-link " + TEST_PKG_NAME + " undefined"); 195 executeShellCommand("pm set-app-link " + YOUTUBE_PKG_NAME + " always"); 196 Thread.sleep(TIMEOUT); 197 mDevice.unfreezeRotation(); 198 mDevice.pressHome(); 199 super.tearDown(); 200 } 201 202 // Start an intent to open a test link openLink(String scheme, String host)203 private void openLink(String scheme, String host) throws InterruptedException { 204 String out = executeShellCommand(String.format( 205 "am start -a android.intent.action.VIEW -d %s://%s/", scheme, host)); 206 Thread.sleep(TIMEOUT * 2); 207 } 208 209 // If framework identifies more than one app that can handle a link intent, framework presents a 210 // window to user to choose the app to handle the intent. 211 // This is also known as 'disambig' window ensureDisambigPresent()212 private void ensureDisambigPresent() { 213 assertNotNull("Disambig dialog is not shown", 214 mDevice.wait(Until.hasObject(By.res("android:id/resolver_list")), 215 TIMEOUT)); 216 List<UiObject2> resolverApps = mDevice.wait(Until.findObjects(By.res("android:id/text1")), 217 TIMEOUT); 218 assertTrue("There aren't exactly 2 apps to resolve", resolverApps.size() == 2); 219 assertTrue("Resolver apps aren't correct", 220 "AppLinkTestApp".equals(resolverApps.get(0).getText()) && 221 "Chrome".equals(resolverApps.get(1).getText())); 222 } 223 224 // Verifies that a certain package is in foreground verifyForegroundAppPackage(String pkgName)225 private void verifyForegroundAppPackage(String pkgName) throws InterruptedException { 226 int counter = 3; 227 List<AccessibilityWindowInfo> windows = null; 228 while (--counter > 0 && windows == null) { 229 windows = mUiAutomation.getWindows(); 230 Thread.sleep(TIMEOUT); 231 } 232 assertTrue(String.format("%s is not top activity", "youtube"), 233 windows.get(windows.size() - 1).getRoot().getPackageName().equals(pkgName)); 234 } 235 236 // Gets app link for a package getAppLink(String pkgName)237 private String getAppLink(String pkgName) { 238 return executeShellCommand(String.format("pm get-app-link %s", pkgName)); 239 } 240 241 // Sets Openlink settings for a package to passed value setAppLink(String pkgName, String valueToBeSet)242 private void setAppLink(String pkgName, String valueToBeSet) { 243 executeShellCommand(String.format("pm set-app-link %s %s", pkgName, valueToBeSet)); 244 } 245 246 // Executes 'adb shell' command. Converts ParcelFileDescriptor output to String executeShellCommand(String command)247 private String executeShellCommand(String command) { 248 if (command == null || command.isEmpty()) { 249 return null; 250 } 251 ParcelFileDescriptor pfd = mUiAutomation.executeShellCommand(command); 252 try (BufferedReader reader = new BufferedReader( 253 new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) { 254 String str = reader.readLine(); 255 Log.d(TEST_TAG, String.format("Executing command: %s", command)); 256 return str; 257 } catch (IOException e) { 258 Log.e(TEST_TAG, e.getMessage()); 259 } 260 261 return null; 262 } 263 } 264