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 mWmState.waitForNonActivityWindowFocused(); 152 153 waitForDeviceIdle(3000); 154 SystemUtil.runWithShellPermissionIdentity( 155 () -> mInstrumentation.sendStringSync(LOCK_CREDENTIAL)); 156 pressEnterButton(); 157 return this; 158 } 159 removeLockCredential()160 private static void removeLockCredential() { 161 runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL); 162 } 163 164 /** 165 * Disables the lock screen. Clears the secure credential first if one is set. 166 */ disableLockScreen()167 public LockScreenSession disableLockScreen() { 168 // Lock credentials need to be cleared before disabling the lock. 169 if (mLockCredentialSet) { 170 removeLockCredential(); 171 mLockCredentialSet = false; 172 } 173 setLockDisabled(true); 174 return this; 175 } 176 isDisplayOn()177 private boolean isDisplayOn() { 178 final Display display = mDm.getDisplay(DEFAULT_DISPLAY); 179 return display != null && display.getState() == Display.STATE_ON; 180 } 181 182 /** 183 * Puts the device to sleep with intention of locking if a lock is enabled. 184 */ sleepDevice()185 public LockScreenSession sleepDevice() { 186 pressSleepButton(); 187 // Not all device variants lock when we go to sleep, so we need to explicitly lock the 188 // device. Note that pressSleepButton() above is redundant because the action also 189 // puts the device to sleep, but kept around for clarity. 190 if (FeatureUtil.isWatch()) { 191 mInstrumentation.getUiAutomation().performGlobalAction( 192 AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN); 193 } 194 if (mAmbientDisplayConfiguration.alwaysOnEnabled( 195 android.os.Process.myUserHandle().getIdentifier())) { 196 mWmState.waitForAodShowing(); 197 } else { 198 Condition.waitFor("display to turn off", () -> !isDisplayOn()); 199 } 200 if (!isLockDisabled()) { 201 mWmState.waitFor( 202 state -> state.getKeyguardControllerState().keyguardShowing, 203 "Keyguard showing"); 204 } 205 return this; 206 } 207 208 /** 209 * Wakes the device up. 210 */ wakeUpDevice()211 public LockScreenSession wakeUpDevice() { 212 pressWakeupButton(); 213 return this; 214 } 215 216 /** 217 * Unlocks the device by using the unlock button. 218 */ unlockDevice()219 public LockScreenSession unlockDevice() { 220 // Make sure the unlock button event is send to the default display. 221 mTouchHelper.touchAndCancelOnDisplayCenterSync(DEFAULT_DISPLAY); 222 223 pressUnlockButton(); 224 return this; 225 } 226 227 /** 228 * Locks the device and wakes it up so that the keyguard is shown. 229 * @param showWhenLockedActivities Activities to check for after showing the keyguard. 230 */ gotoKeyguard(ComponentName... showWhenLockedActivities)231 public LockScreenSession gotoKeyguard(ComponentName... showWhenLockedActivities) { 232 if (DEBUG && isLockDisabled()) { 233 logE("LockScreenSession.gotoKeyguard() is called without lock enabled."); 234 } 235 sleepDevice(); 236 wakeUpDevice(); 237 if (showWhenLockedActivities.length == 0) { 238 mWmState.waitForKeyguardShowingAndNotOccluded(); 239 } else { 240 mWmState.waitForValidState(showWhenLockedActivities); 241 } 242 return this; 243 } 244 isKeyguardLocked()245 private boolean isKeyguardLocked() { 246 return mKm != null && mKm.isKeyguardLocked(); 247 } 248 249 @Override close()250 public void close() { 251 // If keyguard is occluded, credential cannot be removed as expected. 252 // LockScreenSession#close is always called before stopping all test activities, 253 // which could cause the keyguard to stay occluded after wakeup. 254 // If Keyguard is occluded, pressing the back key can hide the ShowWhenLocked activity. 255 wakeUpDevice(); 256 mWmState.computeState(); 257 if (WindowManagerStateHelper.isKeyguardOccluded(mWmState)) { 258 pressBackButton(); 259 } 260 261 final boolean wasCredentialSet = mLockCredentialSet; 262 boolean wasDeviceLocked = false; 263 if (mLockCredentialSet) { 264 wasDeviceLocked = mKm != null && mKm.isDeviceLocked(); 265 removeLockCredential(); 266 mLockCredentialSet = false; 267 } 268 269 // Restore the initial state. 270 switch (mInitialState) { 271 case LOCK_DISABLED -> setLockDisabled(true); 272 case LOCK_ENABLED -> setLockDisabled(false); 273 } 274 275 if (FeatureUtil.isWatch()) { 276 // Keyguard will be dismissed when the credential is removed. 277 mWmState.waitForKeyguardGone(); 278 } 279 280 if (!isKeyguardLocked()) { 281 // we can return early if keyguard is not locked 282 log("Returning early since keyguard is not locked"); 283 return; 284 } 285 286 // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for 287 // the stale credential. 288 289 // If the credential wasn't set, the steps for restoring can be simpler. 290 if (!wasCredentialSet) { 291 mWmState.computeState(); 292 if (WindowManagerStateHelper.isKeyguardShowingAndNotOccluded(mWmState)) { 293 // Keyguard is showing and not occluded so only need to unlock. 294 unlockDevice(); 295 return; 296 } 297 298 final ComponentName home = mWmState.getHomeActivityName(); 299 if (home != null && mWmState.hasActivityState(home, STATE_RESUMED)) { 300 // Home is resumed so nothing to do (e.g. after finishing show-when-locked app). 301 return; 302 } 303 } 304 305 // If device is unlocked, there might have ShowWhenLocked activity runs on, 306 // use home key to clear all activity at foreground. 307 pressHomeButton(); 308 if (wasDeviceLocked) { 309 // The removal of credential needs an extra cycle to take effect. 310 sleepDevice(); 311 wakeUpDevice(); 312 } 313 if (isKeyguardLocked()) { 314 unlockDevice(); 315 } 316 } 317 318 /** 319 * Returns whether the lock screen is disabled. 320 * 321 * @return true if the lock screen is disabled, false otherwise. 322 */ isLockDisabled()323 private boolean isLockDisabled() { 324 final String isLockDisabled = runCommandAndPrintOutput( 325 "locksettings get-disabled " + oldIfNeeded()).trim(); 326 return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled); 327 } 328 329 /** 330 * Disable the lock screen. 331 * 332 * @param lockDisabled true if should disable, false otherwise. 333 */ setLockDisabled(boolean lockDisabled)334 private void setLockDisabled(boolean lockDisabled) { 335 runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled); 336 } 337 338 @NonNull oldIfNeeded()339 private String oldIfNeeded() { 340 if (mLockCredentialSet) { 341 return " --old " + ActivityManagerTestBase.LOCK_CREDENTIAL + " "; 342 } 343 return ""; 344 } 345 } 346