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