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 android.system.helpers; 18 19 import android.app.KeyguardManager; 20 import android.content.Context; 21 import android.graphics.Point; 22 import android.provider.Settings; 23 import android.support.test.InstrumentationRegistry; 24 import android.support.test.uiautomator.By; 25 import android.support.test.uiautomator.BySelector; 26 import android.support.test.uiautomator.UiDevice; 27 import android.support.test.uiautomator.UiObject2; 28 import android.support.test.uiautomator.Until; 29 import android.system.helpers.ActivityHelper; 30 import android.system.helpers.DeviceHelper; 31 32 import junit.framework.Assert; 33 34 import java.io.IOException; 35 import java.util.regex.Pattern; 36 37 /** 38 * Implement common helper methods for Lockscreen. 39 */ 40 public class LockscreenHelper { 41 private static final String LOG_TAG = LockscreenHelper.class.getSimpleName(); 42 public static final int SHORT_TIMEOUT = 200; 43 public static final int LONG_TIMEOUT = 2000; 44 public static final String EDIT_TEXT_CLASS_NAME = "android.widget.EditText"; 45 public static final String CAMERA2_PACKAGE = "com.android.camera2"; 46 public static final String CAMERA_PACKAGE = "com.google.android.GoogleCamera"; 47 public static final String MODE_PIN = "PIN"; 48 public static final String MODE_PASSWORD = "Password"; 49 public static final String MODE_PATTERN = "Pattern"; 50 private static final int SWIPE_MARGIN = 5; 51 private static final int SWIPE_MARGIN_BOTTOM = 100; 52 private static final int DEFAULT_FLING_STEPS = 5; 53 private static final int DEFAULT_SCROLL_STEPS = 15; 54 private static final String PIN_ENTRY = "com.android.systemui:id/pinEntry"; 55 private static final String SET_PIN_COMMAND = "locksettings set-pin %s"; 56 private static final String SET_PASSWORD_COMMAND = "locksettings set-password %s"; 57 private static final String SET_PATTERN_COMMAND = "locksettings set-pattern %s"; 58 private static final String CLEAR_COMMAND = "locksettings clear --old %s"; 59 private static final String HOTSEAT = "hotseat"; 60 private static final BySelector DONE_BUTTON = 61 By.res("com.android.settings", "redaction_done_button"); 62 63 private static LockscreenHelper sInstance = null; 64 private Context mContext = null; 65 private UiDevice mDevice = null; 66 private final ActivityHelper mActivityHelper; 67 private final CommandsHelper mCommandsHelper; 68 private final DeviceHelper mDeviceHelper; 69 private boolean mIsRyuDevice = false; 70 LockscreenHelper()71 public LockscreenHelper() { 72 mContext = InstrumentationRegistry.getTargetContext(); 73 mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 74 mActivityHelper = ActivityHelper.getInstance(); 75 mCommandsHelper = CommandsHelper.getInstance(InstrumentationRegistry.getInstrumentation()); 76 mDeviceHelper = DeviceHelper.getInstance(); 77 mIsRyuDevice = mDeviceHelper.isRyuDevice(); 78 } 79 getInstance()80 public static LockscreenHelper getInstance() { 81 if (sInstance == null) { 82 sInstance = new LockscreenHelper(); 83 } 84 return sInstance; 85 } 86 getLauncherPackage()87 public String getLauncherPackage() { 88 return mDevice.getLauncherPackageName(); 89 } 90 91 /** 92 * Launch Camera on LockScreen 93 * @return true/false 94 */ launchCameraOnLockScreen()95 public boolean launchCameraOnLockScreen() { 96 // Hit the back button to dismiss any keyguard 97 mDevice.pressBack(); 98 String CameraPackage = mIsRyuDevice ? CAMERA2_PACKAGE : CAMERA_PACKAGE; 99 int w = mDevice.getDisplayWidth(); 100 int h = mDevice.getDisplayHeight(); 101 // Load camera on LockScreen and take a photo 102 mDevice.drag((w - 25), (h - 25), (int) (w * 0.5), (int) (w * 0.5), 40); 103 mDevice.waitForIdle(); 104 return mDevice.wait(Until.hasObject( 105 By.res(CameraPackage, "activity_root_view")), 106 LONG_TIMEOUT * 2); 107 } 108 109 /** 110 * Sets the screen lock pin or password 111 * @param pwd text of Password or Pin for lockscreen 112 * @param mode indicate if its password or PIN 113 * @throws InterruptedException 114 */ setScreenLock(String pwd, String mode, boolean mIsNexusDevice)115 public void setScreenLock(String pwd, String mode, boolean mIsNexusDevice) 116 throws InterruptedException { 117 if (mode.equalsIgnoreCase("None")) { 118 mDevice.wait(Until.findObject(By.text("None")), LONG_TIMEOUT * 2).click(); 119 return; 120 } 121 enterScreenLockOnce(pwd, mode, mIsNexusDevice); 122 Thread.sleep(LONG_TIMEOUT); 123 // Re-enter password on confirmation screen 124 UiObject2 pinField = mDevice.wait(Until.findObject(By.clazz(EDIT_TEXT_CLASS_NAME)), 125 LONG_TIMEOUT); 126 pinField.setText(pwd); 127 Thread.sleep(LONG_TIMEOUT); 128 mDevice.pressEnter(); 129 // Click DONE on lock screen notification setting screen 130 mDevice.wait(Until.findObject(DONE_BUTTON), LONG_TIMEOUT).click(); 131 } 132 133 /** 134 * Enters the screen lock once on the setting screen 135 * @param pwd text of Password or Pin for lockscreen 136 * @param mode indicate if its password or PIN 137 * @throws InterruptedException 138 */ enterScreenLockOnce(String pwd, String mode, boolean mIsNexusDevice)139 public void enterScreenLockOnce(String pwd, String mode, boolean mIsNexusDevice) { 140 mDevice.wait(Until.findObject(By.text(mode)), LONG_TIMEOUT * 2).click(); 141 // set up Secure start-up page 142 if (!mIsNexusDevice) { 143 mDevice.wait(Until.findObject(By.text("No thanks")), LONG_TIMEOUT).click(); 144 } 145 UiObject2 pinField = mDevice.wait(Until.findObject(By.clazz(EDIT_TEXT_CLASS_NAME)), 146 LONG_TIMEOUT); 147 pinField.setText(pwd); 148 // enter 149 mDevice.pressEnter(); 150 } 151 152 /* 153 * Enters non matching passcodes on both setting screens. 154 * Note: this will fail if you enter matching passcodes. 155 */ enterNonMatchingPasscodes(String firstPasscode, String secondPasscode, String mode, boolean mIsNexusDevice)156 public void enterNonMatchingPasscodes(String firstPasscode, String secondPasscode, 157 String mode, boolean mIsNexusDevice) throws Exception { 158 enterScreenLockOnce(firstPasscode, mode, mIsNexusDevice); 159 Thread.sleep(LONG_TIMEOUT); 160 UiObject2 pinField = mDevice.wait(Until.findObject(By.clazz(EDIT_TEXT_CLASS_NAME)), 161 LONG_TIMEOUT); 162 pinField.setText(secondPasscode); 163 mDevice.pressEnter(); 164 Thread.sleep(LONG_TIMEOUT); 165 // Verify that error is thrown. 166 UiObject2 dontMatchMessage = mDevice.wait(Until.findObject 167 (By.textContains("don’t match")), LONG_TIMEOUT); 168 Assert.assertNotNull("Error message for passcode confirmation not visible", 169 dontMatchMessage); 170 } 171 172 /** 173 * check if Emergency Call page exists 174 * @throws InterruptedException 175 */ checkEmergencyCallOnLockScreen()176 public void checkEmergencyCallOnLockScreen() throws InterruptedException { 177 mDevice.pressMenu(); 178 mDevice.wait(Until.findObject(By.text("EMERGENCY")), LONG_TIMEOUT).click(); 179 Thread.sleep(LONG_TIMEOUT); 180 UiObject2 dialButton = mDevice.wait(Until.findObject(By.desc("dial")), 181 LONG_TIMEOUT); 182 Assert.assertNotNull("Can't reach emergency call page", dialButton); 183 mDevice.pressBack(); 184 Thread.sleep(LONG_TIMEOUT); 185 } 186 187 /** 188 * remove Screen Lock, reset to Swipe. 189 * @throws InterruptedException 190 */ removeScreenLock(String pwd)191 public void removeScreenLock(String pwd) 192 throws InterruptedException { 193 navigateToScreenLock(); 194 UiObject2 pinField = mDevice.wait(Until.findObject(By.clazz(EDIT_TEXT_CLASS_NAME)), 195 LONG_TIMEOUT); 196 pinField.setText(pwd); 197 mDevice.pressEnter(); 198 mDevice.wait(Until.findObject(By.text("Swipe")), LONG_TIMEOUT).click(); 199 mDevice.waitForIdle(); 200 mDevice.wait(Until.findObject(By.text( 201 Pattern.compile("YES, REMOVE", Pattern.CASE_INSENSITIVE))), LONG_TIMEOUT).click(); 202 } 203 204 /** 205 * Enter a screen password or PIN. 206 * Pattern not supported, please use 207 * unlockDeviceWithPattern(String) below. 208 * Method assumes the device is on lockscreen. 209 * with keyguard exposed. It will wake 210 * up the device, swipe up to reveal the keyguard, 211 * and enter the password or pin and hit enter. 212 * @throws InterruptedException, IOException 213 */ unlockScreen(String pwd)214 public void unlockScreen(String pwd) 215 throws InterruptedException, IOException { 216 // Press menu key (82 is the code for the menu key) 217 String command = String.format(" %s %s %s", "input", "keyevent", "82"); 218 mDevice.executeShellCommand(command); 219 Thread.sleep(SHORT_TIMEOUT); 220 Thread.sleep(SHORT_TIMEOUT); 221 // enter password to unlock screen 222 command = String.format(" %s %s %s", "input", "text", pwd); 223 mDevice.executeShellCommand(command); 224 mDevice.waitForIdle(); 225 Thread.sleep(SHORT_TIMEOUT); 226 mDevice.pressEnter(); 227 } 228 229 /** 230 * navigate to screen lock setting page 231 * @throws InterruptedException 232 */ navigateToScreenLock()233 public void navigateToScreenLock() 234 throws InterruptedException { 235 mActivityHelper.launchIntent(Settings.ACTION_SECURITY_SETTINGS); 236 mDevice.wait(Until.findObject(By.text("Screen lock")), LONG_TIMEOUT).click(); 237 } 238 239 /** 240 * check if Lock Screen is enabled 241 */ isLockScreenEnabled()242 public boolean isLockScreenEnabled() { 243 KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 244 return km.isKeyguardSecure(); 245 } 246 247 /** 248 * Sets a screen lock via shell. 249 */ setScreenLockViaShell(String passcode, String mode)250 public void setScreenLockViaShell(String passcode, String mode) throws Exception { 251 switch (mode) { 252 case MODE_PIN: 253 mCommandsHelper.executeShellCommand(String.format(SET_PIN_COMMAND, passcode)); 254 break; 255 case MODE_PASSWORD: 256 mCommandsHelper.executeShellCommand(String.format(SET_PASSWORD_COMMAND, passcode)); 257 break; 258 case MODE_PATTERN: 259 mCommandsHelper.executeShellCommand(String.format(SET_PATTERN_COMMAND, passcode)); 260 break; 261 default: 262 throw new IllegalArgumentException("Unsupported mode: " + mode); 263 } 264 } 265 266 /** 267 * Removes the screen lock via shell. 268 */ removeScreenLockViaShell(String pwd)269 public void removeScreenLockViaShell(String pwd) throws Exception { 270 mCommandsHelper.executeShellCommand(String.format(CLEAR_COMMAND, pwd)); 271 } 272 273 /** 274 * swipe up to unlock the screen 275 */ unlockScreenSwipeUp()276 public void unlockScreenSwipeUp() throws Exception { 277 mDevice.wakeUp(); 278 mDevice.waitForIdle(); 279 mDevice.swipe(mDevice.getDisplayWidth() / 2, 280 mDevice.getDisplayHeight() - SWIPE_MARGIN, 281 mDevice.getDisplayWidth() / 2, 282 SWIPE_MARGIN, 283 DEFAULT_SCROLL_STEPS); 284 mDevice.waitForIdle(); 285 } 286 287 /* 288 * Takes in the correct code (pin or password), the attempted 289 * code (pin or password), the mode for the code (whether pin or password) 290 * and whether or not they are expected to match. 291 * Asserts that the device has been successfully unlocked (or not). 292 */ setAndEnterLockscreenCode(String actualCode, String attemptedCode, String mode, boolean shouldMatch)293 public void setAndEnterLockscreenCode(String actualCode, String attemptedCode, 294 String mode, boolean shouldMatch) throws Exception { 295 setScreenLockViaShell(actualCode, mode); 296 Thread.sleep(LONG_TIMEOUT); 297 enterLockscreenCode(actualCode, attemptedCode, mode, shouldMatch); 298 } 299 enterLockscreenCode(String actualCode, String attemptedCode, String mode, boolean shouldMatch)300 public void enterLockscreenCode(String actualCode, String attemptedCode, 301 String mode, boolean shouldMatch) throws Exception { 302 mDevice.pressHome(); 303 mDeviceHelper.sleepAndWakeUpDevice(); 304 unlockScreen(attemptedCode); 305 checkForHotseatOnHome(shouldMatch); 306 removeScreenLockViaShell(actualCode); 307 Thread.sleep(LONG_TIMEOUT); 308 mDevice.pressHome(); 309 } 310 311 /* 312 * Takes in the correct pattern, the attempted pattern, 313 * and whether or not they are expected to match. 314 * Asserts that the device has been successfully unlocked (or not). 315 */ setAndEnterLockscreenPattern(String actualPattern, String attemptedPattern, boolean shouldMatch)316 public void setAndEnterLockscreenPattern(String actualPattern, 317 String attemptedPattern, boolean shouldMatch) throws Exception { 318 setScreenLockViaShell 319 (actualPattern, LockscreenHelper.MODE_PATTERN); 320 unlockDeviceWithPattern(attemptedPattern); 321 checkForHotseatOnHome(shouldMatch); 322 removeScreenLockViaShell(actualPattern); 323 Thread.sleep(LONG_TIMEOUT); 324 mDevice.pressHome(); 325 } 326 checkForHotseatOnHome(boolean deviceUnlocked)327 public void checkForHotseatOnHome(boolean deviceUnlocked) throws Exception { 328 mDevice.pressHome(); 329 Thread.sleep(LONG_TIMEOUT); 330 UiObject2 hotseat = mDevice.findObject(By.res(getLauncherPackage(), HOTSEAT)); 331 if (deviceUnlocked) { 332 Assert.assertNotNull("Device not unlocked correctly", hotseat); 333 } 334 else { 335 Assert.assertNull("Device should not be unlocked", hotseat); 336 } 337 } 338 339 /* 340 * The pattern below is always invalid as you need at least 341 * four dots for a valid lock. That action of changing 342 * directions while dragging is unsupported by 343 * uiautomator. 344 */ enterInvalidPattern()345 public void enterInvalidPattern() throws Exception { 346 // Get coordinates for left top dot 347 UiObject2 lockPattern = mDevice.wait(Until.findObject 348 (By.res("com.android.systemui:id/lockPatternView")), 349 LONG_TIMEOUT); 350 // Get coordinates for left side dots 351 int xCoordinate =(int) (lockPattern.getVisibleBounds().left + 352 lockPattern.getVisibleBounds().left*0.16); 353 int y1Coordinate = (int) (lockPattern.getVisibleBounds().top + 354 lockPattern.getVisibleBounds().top*0.16); 355 int y2Coordinate = (int) (lockPattern.getVisibleBounds().bottom - 356 lockPattern.getVisibleBounds().bottom*0.16); 357 // Drag coordinates from one point to another 358 mDevice.swipe(xCoordinate, y1Coordinate, xCoordinate, y2Coordinate, 2); 359 } 360 361 /* Valid pattern unlock attempt 362 * Takes in a contiguous string as input 363 * 1 2 3 364 * 4 5 6 365 * 7 8 9 366 * with each number representing a dot. Eg: "1236" 367 */ unlockDeviceWithPattern(String unlockPattern)368 public void unlockDeviceWithPattern(String unlockPattern) throws Exception { 369 mDeviceHelper.sleepAndWakeUpDevice(); 370 unlockScreenSwipeUp(); 371 Point[] coordinateArray = new Point[unlockPattern.length()]; 372 for (int i=0; i < unlockPattern.length(); i++) { 373 coordinateArray[i] = calculateCoordinatesForPatternDot(unlockPattern.charAt(i), 374 "com.android.systemui:id/lockPatternView"); 375 } 376 // Note: 50 controls the speed of the pattern drawing. 377 mDevice.swipe(coordinateArray, 50); 378 Thread.sleep(SHORT_TIMEOUT); 379 } 380 381 /* Pattern lock setting attempt 382 * Takes in a contiguous string as input 383 * 1 2 3 384 * 4 5 6 385 * 7 8 9 386 * with each number representing a dot. Eg: "1236" 387 */ enterPatternLockOnceForSettingLock(String unlockPattern)388 public void enterPatternLockOnceForSettingLock(String unlockPattern) 389 throws InterruptedException { 390 Point[] coordinateArray = new Point[unlockPattern.length()]; 391 for (int i=0; i < unlockPattern.length(); i++) { 392 coordinateArray[i] = calculateCoordinatesForPatternDot(unlockPattern.charAt(i), 393 "com.android.settings:id/lockPattern"); 394 } 395 // Note: 50 controls the speed of the pattern drawing. 396 mDevice.swipe(coordinateArray, 50); 397 Thread.sleep(SHORT_TIMEOUT); 398 } 399 400 /* Pattern lock setting - this enters and reconfirms pattern to set 401 * using the UI. 402 * Takes in a contiguous string as input 403 * 1 2 3 404 * 4 5 6 405 * 7 8 9 406 * with each number representing a dot. Eg: "1236" 407 */ setPatternLockSettingLock(String unlockPattern)408 public void setPatternLockSettingLock(String unlockPattern) throws Exception { 409 // Enter the same pattern twice, once on the initial set 410 // screen and once on the confirmation screen. 411 for (int i=0; i<2; i++) { 412 enterPatternLockOnceForSettingLock(unlockPattern); 413 mDevice.pressEnter(); 414 } 415 mDevice.wait(Until.findObject(By.text("DONE")), LONG_TIMEOUT).click(); 416 } 417 418 /* Returns screen coordinates for each pattern dot 419 * for the current device 420 * Represented as follows by chars 421 * 1 2 3 422 * 4 5 6 423 * 7 8 9 424 * this is consistent with the set-pattern command 425 * to avoid confusion. 426 */ calculateCoordinatesForPatternDot(char dotNumber, String lockPatternResId)427 private Point calculateCoordinatesForPatternDot(char dotNumber, String lockPatternResId) { 428 UiObject2 lockPattern = mDevice.wait(Until.findObject 429 (By.res(lockPatternResId)), LONG_TIMEOUT); 430 // Calculate x coordinate 431 int xCoordinate = 0; 432 int deltaX = (int) ((lockPattern.getVisibleBounds().right - 433 lockPattern.getVisibleBounds().left)*0.16); 434 if (dotNumber == '1' || dotNumber == '4' || dotNumber == '7') { 435 xCoordinate = lockPattern.getVisibleBounds().left + deltaX; 436 } 437 else if (dotNumber == '2' || dotNumber == '5' || dotNumber == '8') { 438 xCoordinate = lockPattern.getVisibleCenter().x; 439 } 440 else if (dotNumber == '3' || dotNumber == '6' || dotNumber == '9') { 441 xCoordinate = lockPattern.getVisibleBounds().right - deltaX; 442 } 443 // Calculate y coordinate 444 int yCoordinate = 0; 445 int deltaY = (int) ((lockPattern.getVisibleBounds().bottom - 446 lockPattern.getVisibleBounds().top)*0.16); 447 if (dotNumber == '1' || dotNumber == '2' || dotNumber == '3') { 448 yCoordinate = lockPattern.getVisibleBounds().top + deltaY; 449 } 450 else if (dotNumber == '4' || dotNumber == '5' || dotNumber == '6') { 451 yCoordinate = lockPattern.getVisibleCenter().y; 452 } 453 else if (dotNumber == '7' || dotNumber == '8' || dotNumber == '9') { 454 yCoordinate = lockPattern.getVisibleBounds().bottom - deltaY; 455 } 456 return new Point(xCoordinate, yCoordinate); 457 } 458 } 459