caipivara
4/25/2014 - 9:55 AM

Disable animations for Espresso tests - run with `gradle cATDD`

Disable animations for Espresso tests - run with gradle cATDD

apply plugin: 'android'

android {
    // ...
    productFlavors {
        dev {
            // The one for development/testing
        }
        live {
            // The flavour for releasing
        }
    }
}
// ...

task grantAnimationPermission(type: Exec, dependsOn: 'installDevDebug') { // or install{productFlavour}{buildType}
    commandLine "adb shell pm grant $android.defaultConfig.packageName android.permission.SET_ANIMATION_SCALE".split(' ')
}

tasks.whenTaskAdded { task ->
    if (task.name.startsWith('connectedAndroidTest')) {
        task.dependsOn grantAnimationPermission
    }
}

def copyAndReplaceText(source, dest, Closure replaceText) {
    dest.write(replaceText(source.text))
}

// Override Data in Manifest - This can be done using different Manifest files for each flavor, this way there's no need to modify the manifest
android.applicationVariants.all { variant ->
    if (variant.name.startsWith('dev')) { // Where dev is the one you'll use to run Espresso tests
        System.out.println("Not removing the SET_ANIMATION_SCALE permission for $variant.name")
        return
    }

    System.out.println("Removing the SET_ANIMATION_SCALE permission for $variant.name")
    variant.processManifest.doLast {
        copyAndReplaceText(manifestOutputFile, manifestOutputFile) {
            def replaced = it.replace('<uses-permission android:name="android.permission.SET_ANIMATION_SCALE"/>', '');
            if (replaced.contains('SET_ANIMATION_SCALE')) {
                // For security, imagine an extra space is added before closing tag, then the replace would fail - TODO use regex
                throw new RuntimeException("Don't ship with this permission! android.permission.SET_ANIMATION_SCALE")
            }
            replaced
        }
    }
}
class SystemAnimations {

    private static final String ANIMATION_PERMISSION = "android.permission.SET_ANIMATION_SCALE";
    private static final float DISABLED = 0.0f;
    private static final float DEFAULT = 1.0f;

    private final Context context;

    SystemAnimations(Context context) {
        this.context = context;
    }

    void disableAll() {
        int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
        if (permStatus == PackageManager.PERMISSION_GRANTED) {
            setSystemAnimationsScale(DISABLED);
        }
    }

    void enableAll() {
        int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
        if (permStatus == PackageManager.PERMISSION_GRANTED) {
            setSystemAnimationsScale(DEFAULT);
        }
    }

    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 + " :'(");
        }
    }
}
public class MyInstrumentationTestCase extends ActivityInstrumentationTestCase2<MyActivity> {

    private SystemAnimations systemAnimations;

    public MyInstrumentationTestCase() {
        super(MyActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        systemAnimations = new SystemAnimations(getInstrumentation().getContext());
        systemAnimations.disableAll();
        getActivity();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        systemAnimations.enableAll();
    }
}
<?xml version="1.0" encoding="utf-8"?>
<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.novoda.espresso">
  
  <!-- For espresso testing purposes, this is removed in live builds, but not in dev builds -->
  <uses-permission android:name="android.permission.SET_ANIMATION_SCALE" />

  <!-- ... -->
  
</manifest>