Instrumented Testの実行中のみアニメーションを無効化する

Espressoのセットアップ手順の冒頭 "Set up your test environment" にもある通り、アニメーションをオフにすることが推奨されている。
https://developer.android.com/training/testing/espresso/setup.html
> To avoid flakiness, we highly recommend that you turn off system animations on the virtual or physical devices used for testing. On your device, under Settings > Developer options, disable the following 3 settings:
> 
> * Window animation scale
> * Transition animation scale
> * Animator duration scale

これをやるだけで、EspressoやUI Automatorのテスト成功率はグッと上がる。
ただ、この設定の変更手順は思った以上にめんどくさいので、自動化しておきたいところ。

## adb shell

```sh
adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global animator_duration_scale 0
```

ちなみに、現在の設定値は以下で取得できる。

```sh
adb shell settings get global window_animation_scale
adb shell settings get global transition_animation_scale
adb shell settings get global animator_duration_scale
```

### メリット

* 特別な設定が不要。

### デメリット

* 問答無用で0に上書きしてしまうので、元に戻せない。  
私のように、Nexus 6Pを常用していて、開発でも使うようなケースではこの方法は使いたくない。

put/getを組み合わせれば「0にする・復元する」のバッチは作れそうだが、バッチの実行を自動化するのがめんどくさい感じ。
connectedDebugAndroidTestはAndroid Studio経由のときは呼ばれないっぽいし……ということで、次の案へ。

## TestRuleを作る（API24以降）

こんな感じで、テストの前後でコマンドを実行する。
get/putをtryで囲むかはお好みで。

```NoAnimationTestRule.kt
import android.support.test.InstrumentationRegistry
import android.support.test.uiautomator.UiDevice
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

class NoAnimationTestRule : TestRule {

    override fun apply(base: Statement?, description: Description?): Statement {
        return object : Statement() {
            override fun evaluate() {
                val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
                val backupAnimationScale = getAnimationScale(uiDevice)
                putAnimationScale(uiDevice, disableAnimationScale)
                try {
                    base?.evaluate()
                } finally {
                    putAnimationScale(uiDevice, backupAnimationScale)
                }
            }
        }
    }

    data class AnimationScale(
            val windowAnimationScale: String
            , val transitionAnimationScale: String
            , val animatorDurationScale: String
    )

    val disableAnimationScale = AnimationScale("0.0", "0.0", "0.0")

    fun getAnimationScale(uiDevice: UiDevice): AnimationScale? {
        return try {
            val windowAnimationScale = uiDevice.executeShellCommand("settings get global window_animation_scale")
            val transitionAnimationScale = uiDevice.executeShellCommand("settings get global transition_animation_scale")
            val animatorDurationScale = uiDevice.executeShellCommand("settings get global animator_duration_scale")
            AnimationScale(windowAnimationScale, transitionAnimationScale, animatorDurationScale)
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }

    fun putAnimationScale(uiDevice: UiDevice, animationScale: AnimationScale?) {
        if (animationScale != null) {
            try {
                uiDevice.executeShellCommand("settings put global window_animation_scale ${animationScale.windowAnimationScale}")
                uiDevice.executeShellCommand("settings put global transition_animation_scale ${animationScale.transitionAnimationScale}")
                uiDevice.executeShellCommand("settings put global animator_duration_scale ${animationScale.animatorDurationScale}")
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

}
```

テストコードにはRuleを足すだけで良い。

```MainActivityTest.kt
@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @Rule
    @JvmField
    val rule: ActivityTestRule<MainActivity> = AplinActivityTestRule(MainActivity::class.java, false, false)

    @Rule
    @JvmField
    val noAnimationTestRule = NoAnimationTestRule()

    @Test
    fun test() {
        // test code
    }
}
```

ちなみに、API2３以下はダメで24以降が使える理由は、この変更の影響っぽい。
https://android.googlesource.com/platform/frameworks/base/+/b52c7330d986e62812fd7c1b77020629e8ff7930
BugとChange-Idはどこを見れば良い？
（わかる人教えてください）

### メリット

* テスト終了時に設定が元に戻るので、常用端末を使ったテストがしやすくなる
* コマンドを手動で実行する必要がなくなる

### デメリット

* API24以降でしか動かなかった  
→ Build.VERSION.SDK_INTでget/set内をガードする（妥協案）  
→ minSdkVersionを24にしてしまう（過激案）