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