24

I've been writing tests with androids new espresso framework and find that it works well. One annoying thing (not particular to espresso) is that I have to make sure my screen is awake and unlocked for the tests to run. I found a workaround (through various sources) however I am not sure the best way to integrate it.

So this is what I did, in my "Home" activity I have the following code:

Home.class:

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /************ Put this in a conditional for a test version ***********/
    KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
    KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("TAG");
    keyguardLock.disableKeyguard();
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}

Also you need to add the following permissions:

<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

So after doing this my tests now wake my phone up to run so I don't have to stand guard and make sure that the screen doesn't turn off right before the tests start.

I would rather not include those permissions though in my app obviously. I know with gradle it is possible to make different "flavors" that have their own android manifest which will merge into the main manifest. I was thinking of using that but I'd rather not add a flavor just for that reason given this is already using the test build type to run. It looks like from the android gradle documentation that you can't create an AndroidManifest for the instrumentTest directory as it will be auto generated.

However I was wondering if there is another way to do it without creating a variant and then specifying that the tests should run that variant. Also I'm not sure of the exact syntax of all that and thought it would be nice just to have this information on the site for others as it seems to be scattered about.

Lastly, if anyone knows of a better way of solving the issue of waking the phone up for the tests I'd love to hear it as I'm not a big fan of this way I'm attempting.

Matt Wolfe
  • 8,924
  • 8
  • 60
  • 77
  • Wouldn't a simple power manager partial wake lock do ? – Mr_and_Mrs_D Nov 09 '13 at 13:03
  • I dont want to keep my app awake for the duration of the test (that happens on it's own), I want to wake it up to start the test. I would prefer if I could do the wake up from the test code itself rather than the app but I don't know how. – Matt Wolfe Nov 09 '13 at 19:29
  • The mechanism to wake applications is the AlarmManager. Don't know about espresso, sorry – Mr_and_Mrs_D Nov 09 '13 at 19:37

9 Answers9

16

I actually figured out a really easy way to handle this. Remove the keyguard and wakelock permissions from the main manifest and put them in src/debug/AndroidManifest.xml like so:

src/debug/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
</manifest>

When the app is built for debug the above permissions will merge into the main manifest. By default the build system uses the debug build for instrument tests so this works fine.

Then in my onCreate I put the code mentioned in the question:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (BuildConfig.DEBUG) {
        KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
        KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("TAG");
        keyguardLock.disableKeyguard();
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
    }
    ...
}

Now my phone can run tests without me waking them up by hand first and I didn't have to add the above permissions to the release version of my app.

Matt Wolfe
  • 8,924
  • 8
  • 60
  • 77
  • 4
    Why should Activity code be modified to fit unit test's requirement? It seems to be an anti-pattern... – IgorGanapolsky Oct 28 '16 at 13:12
  • 1
    Why don't you put the manifest code in the "androidTest" folder and use this code only in your tests? Polluting your code with test boilerplate is a really bad idea. – Patrick May 24 '17 at 09:19
10

The easiest approach is to use adb command like below if you are running the tests from CI environment for example:

adb -s $DEVICE_ID shell input keyevent 82

This will unlock your device screen.

denys
  • 6,834
  • 3
  • 37
  • 36
9

Now that KeyguardLock is deprecated, you can simply use:

    if (BuildConfig.DEBUG) {
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
    }
Raphael C
  • 2,296
  • 1
  • 22
  • 22
5

I've created the src/debug/AndroidManifest.xml file as Matt suggested and added to following code to the testCase:

   @Override
    public void setUp() throws Exception {
        super.setUp();
        // Espresso will not launch our activity for us, we must launch it via getActivity().
        Activity activity = getActivity();
        KeyguardManager km = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);
        KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("TAG");
        keyguardLock.disableKeyguard();
        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);

    }
userM1433372
  • 5,345
  • 35
  • 38
  • 1
    This works great, except it results in errors about touching the view hierarchy from the wrong thread, at least for me (I'm using Robotium). The solution for me was to run the code inside of a `getInstrumentation().runOnMainSync()` block. – Avi Cherry Mar 11 '16 at 18:20
4

Although you already have accepted Matt Wolve answer, I think that polluting your code with test boilerplate is not a good idea (I am aware that using Espresso there are some situations where you have to, like adding idle flags for custom IdlingResources). I would like to add another approach:

    @ClassRule
    public static ActivityTestRule<HomeActivity> mActivityRuleSetUp = new ActivityTestRule<>(
            HomeActivity.class);


    private static void wakeUpDevice(){
        if (BuildConfig.DEBUG){
            HomeActivity homeActivity = mActivityRuleSetUp.getActivity();

            KeyguardManager myKM = (KeyguardManager) homeActivity.getSystemService(HomeActivity.KEYGUARD_SERVICE);
            boolean isPhoneLocked = myKM.inKeyguardRestrictedInputMode();

            if (isPhoneLocked){
                homeActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
            }
        }
     }


    @BeforeClass
    public static void setUp(){
        wakeUpDevice();
    }

Hope it helps.

Shondeslitch
  • 1,049
  • 1
  • 13
  • 26
  • could you elaborate more on this approach? It seems like what I'm trying to do but don't entirely understand how to go about the implementation. Any links to examples? – Brian Anderson Jan 20 '18 at 16:13
  • There is a missing bracket btw for the method wakeUpDevice(). Why are the methods static btw? What is the point in that? – TheOddCoder Oct 02 '18 at 08:51
  • Thanks TheOddCoder for the bracket. Apologies but to be honest I can't remember if they were static by restrictions I couldn't manage. Just to say that I always try to avoid every "static" modifier if don't require. – Shondeslitch Oct 03 '18 at 10:45
4

Here is an approach that does not use any deprecated APIs, no manifest permissions, and works even if the device has a lock pin set.

Option 1: In @Before

@RunWith(AndroidJUnit4::class)
class MyActivityTest {

    // Replace with your own Activity test rule.
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Before
    fun setUp() {
        UiDevice.getInstance(getInstrumentation()).wakeUp()
        composeTestRule.activity.setShowWhenLocked(true)
    }
}

Option 2: Rule

@RunWith(AndroidJUnit4::class)
class MyActivityTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @get:Rule
    val screenLockRule = RunWhenScreenOffOrLockedRule()
}

/**
* This rule will allow the tests to run even if the device's screen is off or locked.
* Allows a developer to fire and forget running the UI Test across different devices or on the CI
* emulator.
*/
class RunWhenScreenOffOrLockedRule : TestRule {
    override fun apply(base: Statement, description: Description): Statement {
        return object : Statement() {

            override fun evaluate() {
                // Turn screen on
                UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).wakeUp()

                // Allow any activity to run when locked
                ActivityLifecycleMonitorRegistry
                    .getInstance()
                    .addLifecycleCallback { activity, stage ->
                        if (stage === Stage.PRE_ON_CREATE) {
                            activity.setShowWhenLocked(true)
                        }
                    }

                // Continue with other statements
                base.evaluate()
            }
        }
    }
}
ryanholden8
  • 536
  • 4
  • 13
1

For testing device set Lock pattern to NONE in Settings->Security Then use instance of UiDevice and call its wakeUp() method

This method simulates pressing the power button if the screen is OFF else it does nothing if the screen is already ON. If the screen was OFF and it just got turned ON, this method will insert a 500ms delay to allow the device time to wake up and accept input.

Ewoks
  • 12,285
  • 8
  • 58
  • 67
1

Another best way to wake up device before test. Simply add ActivityLifecycleCallback in your setUp method.

public class Moduletest extends ActivityInstrumentationTestCase2<Your Activity>{

 protected void setUp(){
    super.setUp();

    ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(new ActivityLifecycleCallback() {
      @Override public void onActivityLifecycleChanged(Activity activity, Stage stage) {
        if (stage == Stage.PRE_ON_CREATE) {
          activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
      }
    });
  }
}
Divyang Desai
  • 7,483
  • 13
  • 50
  • 76
1

I have done it this way: First make two rules, one for the activity and one for UI Thread:

@Rule
public ActivityTestRule<Your Activity> mActivityRule =
        new ActivityTestRule<>(Your Activity.class, true, true);

@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();

And then in my first test method made this:

   @Test
   @LargeTest
   public void CreateAndSaveTaskEntity() throws Throwable {

            uiThreadTestRule.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Your Activity activity = mActivityRule.getActivity();
            activity.getWindow()
                    .addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
                            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);

                }
            });

            //begin test cases

            ...
    }

Of course you have to add in AndroidManifest.xml the permissions:

<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
MeLean
  • 3,092
  • 6
  • 29
  • 43