Skip to content

Instantly share code, notes, and snippets.

@xrigau
Last active October 22, 2022 02:36
Show Gist options
  • Save xrigau/11284124 to your computer and use it in GitHub Desktop.
Save xrigau/11284124 to your computer and use it in GitHub Desktop.
Disable animations for Espresso tests - run with `gradle cATDD`
<?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>
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
}
}
}
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();
}
}
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 + " :'(");
}
}
}
@twyatt
Copy link

twyatt commented Jan 3, 2017

@tasomaniac Not sure what I'm doing wrong but issuing any of the adb commands you listed in your Jan 4, 2016 comment doesn't have any effect until I restart my emulator?

Tried with Nexus 5 API 21 AVD and with Genymotion running an API 21 image; neither would change animation scales using adb until I restarted the emulator.

@Kisty
Copy link

Kisty commented Jun 2, 2017

Have you seen https://github.com/linkedin/test-butler? Does the same thing but automatically, just need to install a tiny app on emulator.

Gradle support to auto install the test helper app works from 3.0.0-alpha1 or using a little gradle script

@eighthave
Copy link

Why not just make a manifest in the existing androidTest flavor? I.e. app/src/androidTest/AndroidManifest.xml. Then you don't need any special build flavor to include this.

@eighthave
Copy link

I see, because the disable animations thing is happening in the app, rather than in the instrumentation. If someone knows how to run it in the instrumentation, it would eliminate the need for build flavors and other gradle tricks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment