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