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