caipivara
7/15/2015 - 3:46 PM

Android - AndroidJUnitRunner that disable animations, disable screen lock and wake processor all the time to avoid Tests to fail because of

Android - AndroidJUnitRunner that disable animations, disable screen lock and wake processor all the time to avoid Tests to fail because of test device setup. Note that my test buildType is mock to have a manifest just for tests (dont want to ship an app with SET_ANIMATION_SCALE permissions...).

#!/bin/bash
#
# source https://github.com/zielmicha/adb-wrapper
#
# argument: apk package
# Set permission android.permission.SET_ANIMATION_SCALE for each device.
# ex: sh set_animation_permissions.sh <package>
#

adb=$ANDROID_HOME/platform-tools/adb
package=$1

if [ "$#" = 0 ]; then
    echo "No parameters found, run with sh set_animation_permissions.sh <package>"
    exit 0
fi

# get all the devices
devices=$($adb devices | grep -v 'List of devices' | cut -f1 | grep '.')

for device in $devices; do
    echo "Setting permissions to device" $device "for package" $package
    $adb -s $device shell pm grant $package android.permission.SET_ANIMATION_SCALE
done
def appId = "com.example.tests"
def appVersionName = "1.0"
def appVersionCode = 2

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId appId
        minSdkVersion 15
        targetSdkVersion 23
        versionCode appVersionCode
        versionName appVersionName

        multiDexEnabled = true // Avoid Bug with java 1.7
        testInstrumentationRunner "com.example.tests.PreparerTestRunner"
        //testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        testBuildType "mock"
    }
}

dependencies {
    androidTestCompile 'com.android.support.test:runner:0.4'
    androidTestCompile 'com.android.support.test:rules:0.4'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
    androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1'
    androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.1') {
        // this library uses the newest app compat v22 but the espresso contrib still v21.
        // you have to specifically exclude the older versions of the contrib library or
        // there will be some conflicts
        exclude group: 'com.android.support', module: 'appcompat'
        exclude group: 'com.android.support', module: 'support-v4'
        exclude module: 'recyclerview-v7'
    }
    androidTestCompile 'junit:junit:4.12'
    androidTestCompile 'com.squareup.retrofit:retrofit-mock:1.9.0'
    androidTestCompile 'com.squareup.assertj:assertj-android:1.1.0'
    androidTestCompile 'com.squareup.spoon:spoon-client:1.2.0'
}

// Grant animation permissions to avoid test failure because of ui sync.
task grantAnimationPermissions(type: Exec, dependsOn: 'installMock') {
    group = 'test'
    description = 'Grant permissions for testing.'

    def absolutePath = file('..') // Get project absolute path
    commandLine "$absolutePath/set_animation_permissions.sh $appId".split(" ")
}

// Source: http://stackoverflow.com/q/29908110/112705
afterEvaluate {
    // When launching individual tests from Android Studio, it seems that only the assemble tasks
    // get called directly, not the install* versions
    tasks.each { task ->
        if (task.name.startsWith('assembleMockAndroidTest')) {
            task.dependsOn grantAnimationPermissions
        }
    }
}
import android.Manifest;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.test.runner.AndroidJUnitRunner;
import android.util.Log;

import java.lang.reflect.Method;

import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP;
import static android.os.PowerManager.FULL_WAKE_LOCK;
import static android.os.PowerManager.ON_AFTER_RELEASE;
import static android.support.test.InstrumentationRegistry.getInstrumentation;

/**
 * Tests can fail for other reasons than code, it´ because of the animations and espresso sync and
 * emulator state (screen off or locked)
 *
 * Before all the tests prepare the device to run tests and avoid these problems.
 *
 * - Disable animations
 * - Disable keyguard lock
 * - Set it to be awake all the time (dont let the processor sleep)
 *
 * @see <a href="u2020 open source app by Jake Wharton">https://github.com/JakeWharton/u2020</a>
 * @see <a href="Daj gist">https://gist.github.com/daj/7b48f1b8a92abf960e7b</a>
 * @see <a href="Android-test-kit Disabling Animations">https://code.google.com/p/android-test-kit/wiki/DisablingAnimations</a>
 */
public final class PreparerTestRunner extends AndroidJUnitRunner {

    @Override
    public void onStart() {

        runOnMainSync(() -> {
            Context app = getTargetContext().getApplicationContext();

            disableAnimations(app);

            String name = PreparerTestRunner.class.getSimpleName();
            // Unlock the device so that the tests can input keystrokes.
            KeyguardManager keyguard = (KeyguardManager) app.getSystemService(KEYGUARD_SERVICE);
            keyguard.newKeyguardLock(name).disableKeyguard();
            // Wake up the screen.
            PowerManager power = (PowerManager) app.getSystemService(POWER_SERVICE);
            power.newWakeLock(FULL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP | ON_AFTER_RELEASE, name)
                    .acquire();
        });

        super.onStart();
    }

    @Override
    public void finish(int resultCode, Bundle results) {
        super.finish(resultCode, results);
        enableAnimations(getInstrumentation().getContext());
    }

    //<editor-fold desc="Animations">
    void disableAnimations(Context context) {
        int permStatus = context.checkCallingOrSelfPermission(Manifest.permission.SET_ANIMATION_SCALE);
        if (permStatus == PackageManager.PERMISSION_GRANTED) {
            setSystemAnimationsScale(0.0f);
        }
    }

    void enableAnimations(Context context) {
        int permStatus = context.checkCallingOrSelfPermission(Manifest.permission.SET_ANIMATION_SCALE);
        if (permStatus == PackageManager.PERMISSION_GRANTED) {
            setSystemAnimationsScale(1.0f);
        }
    }

    private void setSystemAnimationsScale(float animationScale) {
        try {
            Class<?> windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub");
            Method asInterface = windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class);
            Class<?> serviceManagerClazz = Class.forName("android.os.ServiceManager");
            Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class);
            Class<?> windowManagerClazz = Class.forName("android.view.IWindowManager");
            Method setAnimationScales = windowManagerClazz.getDeclaredMethod("setAnimationScales", float[].class);
            Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales");

            IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window");
            Object windowManagerObj = asInterface.invoke(null, windowManagerBinder);
            float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj);
            for (int i = 0; i < currentScales.length; i++) {
                currentScales[i] = animationScale;
            }
            setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales});
        } catch (Exception e) {
            Log.e("SystemAnimations", "Could not change animation scale to " + animationScale + " :'(");
        }
    }
    //</editor-fold>
}
<?xml version="1.0" encoding="utf-8"?>
<!-- This file should be outside of release manifest (in this case app/src/mock/Manifest.xml -->
<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.tests">
  
  <!-- For espresso testing purposes, this is removed in live builds, but not in dev builds -->
  <uses-permission android:name="android.permission.SET_ANIMATION_SCALE" />
  <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
  <uses-permission android:name="android.permission.WAKE_LOCK" />

</manifest>