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