1 /*
2  * Copyright (C) 2023 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.server.wm;
18 
19 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
20 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
21 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
22 import static android.content.pm.PackageManager.FEATURE_SECURE_LOCK_SCREEN;
23 import static android.content.pm.PackageManager.FEATURE_WATCH;
24 import static android.server.wm.ShellCommandHelper.executeShellCommandAndGetStdout;
25 import static android.server.wm.StateLogger.log;
26 import static android.server.wm.StateLogger.logE;
27 import static android.server.wm.UiDeviceUtils.pressBackButton;
28 import static android.server.wm.UiDeviceUtils.pressEnterButton;
29 import static android.server.wm.UiDeviceUtils.pressHomeButton;
30 import static android.server.wm.UiDeviceUtils.pressSleepButton;
31 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
32 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
33 import static android.server.wm.UiDeviceUtils.waitForDeviceIdle;
34 import static android.server.wm.WindowManagerState.STATE_RESUMED;
35 import static android.view.Display.DEFAULT_DISPLAY;
36 
37 import android.accessibilityservice.AccessibilityService;
38 import android.app.Instrumentation;
39 import android.app.KeyguardManager;
40 import android.content.ComponentName;
41 import android.content.Context;
42 import android.content.res.Resources;
43 import android.hardware.display.AmbientDisplayConfiguration;
44 import android.hardware.display.DisplayManager;
45 import android.view.Display;
46 
47 import androidx.annotation.NonNull;
48 
49 import com.android.compatibility.common.util.FeatureUtil;
50 import com.android.compatibility.common.util.SystemUtil;
51 
52 public class LockScreenSession implements AutoCloseable {
53     enum LockState {
54         LOCK_DISABLED,
55         LOCK_ENABLED
56     }
57 
58     private static final boolean DEBUG = true;
59     private static final String LOCK_CREDENTIAL = "1234";
60 
61     private final Instrumentation mInstrumentation;
62     private final Context mContext;
63 
64     private final LockState mInitialState;
65     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
66 
67     private final WindowManagerStateHelper mWmState;
68     private final TouchHelper mTouchHelper;
69 
70     private final DisplayManager mDm;
71     private final KeyguardManager mKm;
72 
73     private boolean mLockCredentialSet;
74 
LockScreenSession(Instrumentation instrumentation, WindowManagerStateHelper wmState)75     public LockScreenSession(Instrumentation instrumentation, WindowManagerStateHelper wmState) {
76         mInstrumentation = instrumentation;
77         mWmState = wmState;
78         mContext = instrumentation.getContext();
79         mTouchHelper = new TouchHelper(instrumentation, wmState);
80         mDm = mContext.getSystemService(DisplayManager.class);
81         mKm = mContext.getSystemService(KeyguardManager.class);
82 
83         // Store the initial state so that it can be restored when the session
84         // goes out of scope.
85         mInitialState = isLockDisabled() ? LockState.LOCK_DISABLED : LockState.LOCK_ENABLED;
86 
87         // Enable lock screen (swipe) by default.
88         setLockDisabled(false);
89         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
90 
91         // On devices that don't support any insecure locks but supports a secure lock, let's
92         // enable a secure lock.
93         if (!supportsInsecureLock() && supportsSecureLock()) {
94             setLockCredential();
95         }
96     }
97 
getSupportsInsecureLockScreen()98     private boolean getSupportsInsecureLockScreen() {
99         boolean insecure;
100         try {
101             insecure = mContext.getResources().getBoolean(
102                     Resources.getSystem().getIdentifier(
103                             "config_supportsInsecureLockScreen", "bool", "android"));
104         } catch (Resources.NotFoundException e) {
105             insecure = true;
106         }
107         return insecure;
108     }
109 
110     /** Whether or not the device supports pin/pattern/password lock. */
supportsSecureLock()111     private boolean supportsSecureLock() {
112         return FeatureUtil.hasSystemFeature(FEATURE_SECURE_LOCK_SCREEN);
113     }
114 
115     /** Whether or not the device supports "swipe" lock. */
supportsInsecureLock()116     private boolean supportsInsecureLock() {
117         return !FeatureUtil.hasAnySystemFeature(
118                 FEATURE_LEANBACK, FEATURE_WATCH, FEATURE_EMBEDDED, FEATURE_AUTOMOTIVE)
119                 && getSupportsInsecureLockScreen();
120     }
121 
runCommandAndPrintOutput(String command)122     protected static String runCommandAndPrintOutput(String command) {
123         final String output = executeShellCommandAndGetStdout(command);
124         log(output);
125         return output;
126     }
127 
128     /**
129      * Sets a credential to use with a secure lock method.
130      */
setLockCredential()131     public LockScreenSession setLockCredential() {
132         if (mLockCredentialSet) {
133             // "set-pin" command isn't idempotent. We need to provide the old credential in
134             // order to change it to a new one. However we never use a different credential in
135             // CTS so we don't need to do anything if the credential is already set.
136             return this;
137         }
138         mLockCredentialSet = true;
139         runCommandAndPrintOutput(
140                 "locksettings set-pin " + LOCK_CREDENTIAL);
141         return this;
142     }
143 
144     /**
145      * Unlocks a device by entering a lock credential.
146      */
enterAndConfirmLockCredential()147     public LockScreenSession enterAndConfirmLockCredential() {
148         // Ensure focus will switch to default display. Meanwhile we cannot tap on center area,
149         // which may tap on input credential area.
150         mTouchHelper.touchAndCancelOnDisplayCenterSync(DEFAULT_DISPLAY);
151 
152         waitForDeviceIdle(3000);
153         SystemUtil.runWithShellPermissionIdentity(
154                 () -> mInstrumentation.sendStringSync(LOCK_CREDENTIAL));
155         pressEnterButton();
156         return this;
157     }
158 
removeLockCredential()159     private static void removeLockCredential() {
160         runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
161     }
162 
163     /**
164      * Disables the lock screen. Clears the secure credential first if one is set.
165      */
disableLockScreen()166     public LockScreenSession disableLockScreen() {
167         // Lock credentials need to be cleared before disabling the lock.
168         if (mLockCredentialSet) {
169             removeLockCredential();
170             mLockCredentialSet = false;
171         }
172         setLockDisabled(true);
173         return this;
174     }
175 
isDisplayOn()176     private boolean isDisplayOn() {
177         final Display display = mDm.getDisplay(DEFAULT_DISPLAY);
178         return display != null && display.getState() == Display.STATE_ON;
179     }
180 
181     /**
182      * Puts the device to sleep with intention of locking if a lock is enabled.
183      */
sleepDevice()184     public LockScreenSession sleepDevice() {
185         pressSleepButton();
186         // Not all device variants lock when we go to sleep, so we need to explicitly lock the
187         // device. Note that pressSleepButton() above is redundant because the action also
188         // puts the device to sleep, but kept around for clarity.
189         if (FeatureUtil.isWatch()) {
190             mInstrumentation.getUiAutomation().performGlobalAction(
191                     AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
192         }
193         if (mAmbientDisplayConfiguration.alwaysOnEnabled(
194                 android.os.Process.myUserHandle().getIdentifier())) {
195             mWmState.waitForAodShowing();
196         } else {
197             Condition.waitFor("display to turn off", () -> !isDisplayOn());
198         }
199         if (!isLockDisabled()) {
200             mWmState.waitFor(
201                     state -> state.getKeyguardControllerState().keyguardShowing,
202                     "Keyguard showing");
203         }
204         return this;
205     }
206 
207     /**
208      * Wakes the device up.
209      */
wakeUpDevice()210     public LockScreenSession wakeUpDevice() {
211         pressWakeupButton();
212         return this;
213     }
214 
215     /**
216      * Unlocks the device by using the unlock button.
217      */
unlockDevice()218     public LockScreenSession unlockDevice() {
219         // Make sure the unlock button event is send to the default display.
220         mTouchHelper.touchAndCancelOnDisplayCenterSync(DEFAULT_DISPLAY);
221 
222         pressUnlockButton();
223         return this;
224     }
225 
226     /**
227      * Locks the device and wakes it up so that the keyguard is shown.
228      * @param showWhenLockedActivities Activities to check for after showing the keyguard.
229      */
gotoKeyguard(ComponentName... showWhenLockedActivities)230     public LockScreenSession gotoKeyguard(ComponentName... showWhenLockedActivities) {
231         if (DEBUG && isLockDisabled()) {
232             logE("LockScreenSession.gotoKeyguard() is called without lock enabled.");
233         }
234         sleepDevice();
235         wakeUpDevice();
236         if (showWhenLockedActivities.length == 0) {
237             mWmState.waitForKeyguardShowingAndNotOccluded();
238         } else {
239             mWmState.waitForValidState(showWhenLockedActivities);
240         }
241         return this;
242     }
243 
isKeyguardLocked()244     private boolean isKeyguardLocked() {
245         return mKm != null && mKm.isKeyguardLocked();
246     }
247 
248     @Override
close()249     public void close() {
250         // If keyguard is occluded, credential cannot be removed as expected.
251         // LockScreenSession#close is always called before stopping all test activities,
252         // which could cause the keyguard to stay occluded after wakeup.
253         // If Keyguard is occluded, pressing the back key can hide the ShowWhenLocked activity.
254         wakeUpDevice();
255         mWmState.computeState();
256         if (WindowManagerStateHelper.isKeyguardOccluded(mWmState)) {
257             pressBackButton();
258         }
259 
260         final boolean wasCredentialSet = mLockCredentialSet;
261         boolean wasDeviceLocked = false;
262         if (mLockCredentialSet) {
263             wasDeviceLocked = mKm != null && mKm.isDeviceLocked();
264             removeLockCredential();
265             mLockCredentialSet = false;
266         }
267 
268         // Restore the initial state.
269         switch (mInitialState) {
270             case LOCK_DISABLED -> setLockDisabled(true);
271             case LOCK_ENABLED -> setLockDisabled(false);
272         }
273 
274         if (FeatureUtil.isWatch()) {
275             // Keyguard will be dismissed when the credential is removed.
276             mWmState.waitForKeyguardGone();
277         }
278 
279         if (!isKeyguardLocked()) {
280             // we can return early if keyguard is not locked
281             log("Returning early since keyguard is not locked");
282             return;
283         }
284 
285         // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
286         // the stale credential.
287 
288         // If the credential wasn't set, the steps for restoring can be simpler.
289         if (!wasCredentialSet) {
290             mWmState.computeState();
291             if (WindowManagerStateHelper.isKeyguardShowingAndNotOccluded(mWmState)) {
292                 // Keyguard is showing and not occluded so only need to unlock.
293                 unlockDevice();
294                 return;
295             }
296 
297             final ComponentName home = mWmState.getHomeActivityName();
298             if (home != null && mWmState.hasActivityState(home, STATE_RESUMED)) {
299                 // Home is resumed so nothing to do (e.g. after finishing show-when-locked app).
300                 return;
301             }
302         }
303 
304         // If device is unlocked, there might have ShowWhenLocked activity runs on,
305         // use home key to clear all activity at foreground.
306         pressHomeButton();
307         if (wasDeviceLocked) {
308             // The removal of credential needs an extra cycle to take effect.
309             sleepDevice();
310             wakeUpDevice();
311         }
312         if (isKeyguardLocked()) {
313             unlockDevice();
314         }
315     }
316 
317     /**
318      * Returns whether the lock screen is disabled.
319      *
320      * @return true if the lock screen is disabled, false otherwise.
321      */
isLockDisabled()322     private boolean isLockDisabled() {
323         final String isLockDisabled = runCommandAndPrintOutput(
324                 "locksettings get-disabled " + oldIfNeeded()).trim();
325         return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled);
326     }
327 
328     /**
329      * Disable the lock screen.
330      *
331      * @param lockDisabled true if should disable, false otherwise.
332      */
setLockDisabled(boolean lockDisabled)333     private void setLockDisabled(boolean lockDisabled) {
334         runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled);
335     }
336 
337     @NonNull
oldIfNeeded()338     private String oldIfNeeded() {
339         if (mLockCredentialSet) {
340             return " --old " + ActivityManagerTestBase.LOCK_CREDENTIAL + " ";
341         }
342         return "";
343     }
344 }
345