diff options
author | Xin Li <delphij@google.com> | 2023-12-08 13:14:14 -0800 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2023-12-08 13:14:14 -0800 |
commit | 48810913bd14f1b6215b053797d92bf0f6e59017 (patch) | |
tree | 4001f5a3a7a2efeee47f63741f610d06ab4036c6 | |
parent | 65bef66686f9ea478626d815d565653cb8802813 (diff) | |
parent | 30a2b10bc538eb43fef45dd97eb74bf52589cd4f (diff) | |
download | systemui-48810913bd14f1b6215b053797d92bf0f6e59017.tar.gz |
Merge Android 14 QPR1
Merged-In: I4762502d8982a4fb1cc143f8c332010b5afe6e96
Bug: 315507370
Change-Id: I12455b9a319d7162ead5edab61433f62314cab05
19 files changed, 661 insertions, 71 deletions
diff --git a/animationlib/Android.bp b/animationlib/Android.bp index d67a5de..5417001 100644 --- a/animationlib/Android.bp +++ b/animationlib/Android.bp @@ -31,6 +31,9 @@ android_library { "src/**/*.java", "src/**/*.kt" ], + resource_dirs: [ + "res" + ], kotlincflags: ["-Xjvm-default=all"], // This library is meant to access only public APIs // do not flip this flag to true diff --git a/animationlib/build.gradle b/animationlib/build.gradle index f9c4485..18ae0e1 100644 --- a/animationlib/build.gradle +++ b/animationlib/build.gradle @@ -11,10 +11,12 @@ android { sourceSets { main { java.srcDirs = ['src'] + res.srcDirs = ['res'] manifest.srcFile 'AndroidManifest.xml' } androidTest { - java.srcDirs = ["tests"] + java.srcDirs = ["tests/src"] + manifest.srcFile 'tests/AndroidManifest.xml' } } compileSdk 33 diff --git a/animationlib/res/interpolator/emphasized_accelerate_interpolator.xml b/animationlib/res/interpolator/emphasized_accelerate_interpolator.xml new file mode 100644 index 0000000..b9e3783 --- /dev/null +++ b/animationlib/res/interpolator/emphasized_accelerate_interpolator.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.3" + android:controlY1="0" + android:controlX2="0.8" + android:controlY2="0.15"/> diff --git a/animationlib/res/interpolator/emphasized_decelerate_interpolator.xml b/animationlib/res/interpolator/emphasized_decelerate_interpolator.xml new file mode 100644 index 0000000..990bcff --- /dev/null +++ b/animationlib/res/interpolator/emphasized_decelerate_interpolator.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.05" + android:controlY1="0.7" + android:controlX2="0.1" + android:controlY2="1"/> diff --git a/animationlib/res/interpolator/emphasized_interpolator.xml b/animationlib/res/interpolator/emphasized_interpolator.xml new file mode 100644 index 0000000..5a48611 --- /dev/null +++ b/animationlib/res/interpolator/emphasized_interpolator.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1"/>
\ No newline at end of file diff --git a/animationlib/res/interpolator/standard_accelerate_interpolator.xml b/animationlib/res/interpolator/standard_accelerate_interpolator.xml new file mode 100644 index 0000000..351862f --- /dev/null +++ b/animationlib/res/interpolator/standard_accelerate_interpolator.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.3" + android:controlY1="0" + android:controlX2="1" + android:controlY2="1"/>
\ No newline at end of file diff --git a/animationlib/res/interpolator/standard_decelerate_interpolator.xml b/animationlib/res/interpolator/standard_decelerate_interpolator.xml new file mode 100644 index 0000000..1aee540 --- /dev/null +++ b/animationlib/res/interpolator/standard_decelerate_interpolator.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0" + android:controlY1="0" + android:controlX2="0" + android:controlY2="1"/>
\ No newline at end of file diff --git a/animationlib/res/interpolator/standard_interpolator.xml b/animationlib/res/interpolator/standard_interpolator.xml new file mode 100644 index 0000000..8cc2a65 --- /dev/null +++ b/animationlib/res/interpolator/standard_interpolator.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.2" + android:controlY1="0" + android:controlX2="0" + android:controlY2="1"/> diff --git a/animationlib/src/com/android/app/animation/Interpolators.java b/animationlib/src/com/android/app/animation/Interpolators.java index 0f3776c..aac4627 100644 --- a/animationlib/src/com/android/app/animation/Interpolators.java +++ b/animationlib/src/com/android/app/animation/Interpolators.java @@ -23,6 +23,7 @@ import android.view.animation.BounceInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; +import android.view.animation.OvershootInterpolator; import android.view.animation.PathInterpolator; /** @@ -59,6 +60,26 @@ public class Interpolators { 0.05f, 0.7f, 0.1f, 1f); + public static final Interpolator EXAGGERATED_EASE; + static { + Path exaggeratedEase = new Path(); + exaggeratedEase.moveTo(0, 0); + exaggeratedEase.cubicTo(0.05f, 0f, 0.133333f, 0.08f, 0.166666f, 0.4f); + exaggeratedEase.cubicTo(0.225f, 0.94f, 0.5f, 1f, 1f, 1f); + EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase); + } + + public static final Interpolator INSTANT = t -> 1; + /** + * All values of t map to 0 until t == 1. This is primarily useful for setting view visibility, + * which should only happen at the very end of the animation (when it's already hidden). + */ + public static final Interpolator FINAL_FRAME = t -> t < 1 ? 0 : 1; + + public static final Interpolator OVERSHOOT_0_75 = new OvershootInterpolator(0.75f); + public static final Interpolator OVERSHOOT_1_2 = new OvershootInterpolator(1.2f); + public static final Interpolator OVERSHOOT_1_7 = new OvershootInterpolator(1.7f); + /* * ============================================================================================ * Standard interpolators. @@ -131,11 +152,27 @@ public class Interpolators { public static final Interpolator FAST_OUT_SLOW_IN_REVERSE = new PathInterpolator(0.8f, 0f, 0.6f, 1f); public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f); + public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f); + public static final Interpolator AGGRESSIVE_EASE_IN_OUT = new PathInterpolator(0.6f,0, 0.4f, 1); + + public static final Interpolator DECELERATED_EASE = new PathInterpolator(0, 0, .2f, 1f); + public static final Interpolator ACCELERATED_EASE = new PathInterpolator(0.4f, 0, 1f, 1f); + public static final Interpolator PREDICTIVE_BACK_DECELERATED_EASE = + new PathInterpolator(0, 0, 0, 1f); public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f); public static final Interpolator ACCELERATE = new AccelerateInterpolator(); + public static final Interpolator ACCELERATE_0_5 = new AccelerateInterpolator(0.5f); + public static final Interpolator ACCELERATE_0_75 = new AccelerateInterpolator(0.75f); + public static final Interpolator ACCELERATE_1_5 = new AccelerateInterpolator(1.5f); + public static final Interpolator ACCELERATE_2 = new AccelerateInterpolator(2); public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator(); + public static final Interpolator DECELERATE = new DecelerateInterpolator(); + public static final Interpolator DECELERATE_1_5 = new DecelerateInterpolator(1.5f); + public static final Interpolator DECELERATE_1_7 = new DecelerateInterpolator(1.7f); + public static final Interpolator DECELERATE_2 = new DecelerateInterpolator(2); public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f); + public static final Interpolator DECELERATE_3 = new DecelerateInterpolator(3f); public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f); public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f, @@ -162,12 +199,77 @@ public class Interpolators { public static final Interpolator TOUCH_RESPONSE_REVERSE = new PathInterpolator(0.9f, 0f, 0.7f, 1f); + public static final Interpolator TOUCH_RESPONSE_ACCEL_DEACCEL = + v -> ACCELERATE_DECELERATE.getInterpolation(TOUCH_RESPONSE.getInterpolation(v)); + + + /** + * Inversion of ZOOM_OUT, compounded with an ease-out. + */ + public static final Interpolator ZOOM_IN = new Interpolator() { + @Override + public float getInterpolation(float v) { + return DECELERATE_3.getInterpolation(1 - ZOOM_OUT.getInterpolation(1 - v)); + } + }; + + public static final Interpolator ZOOM_OUT = new Interpolator() { + + private static final float FOCAL_LENGTH = 0.35f; + + @Override + public float getInterpolation(float v) { + return zInterpolate(v); + } + + /** + * This interpolator emulates the rate at which the perceived scale of an object changes + * as its distance from a camera increases. When this interpolator is applied to a scale + * animation on a view, it evokes the sense that the object is shrinking due to moving away + * from the camera. + */ + private float zInterpolate(float input) { + return (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + input)) / + (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + 1.0f)); + } + }; + + public static final Interpolator SCROLL = new Interpolator() { + @Override + public float getInterpolation(float t) { + t -= 1.0f; + return t*t*t*t*t + 1; + } + }; + + public static final Interpolator SCROLL_CUBIC = new Interpolator() { + @Override + public float getInterpolation(float t) { + t -= 1.0f; + return t*t*t + 1; + } + }; + + private static final float FAST_FLING_PX_MS = 10; + /* * ============================================================================================ * Functions / Utilities * ============================================================================================ */ + public static Interpolator scrollInterpolatorForVelocity(float velocity) { + return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC; + } + + /** + * Create an OvershootInterpolator with tension directly related to the velocity (in px/ms). + * @param velocity The start velocity of the animation we want to overshoot. + */ + public static Interpolator overshootInterpolatorForVelocity(float velocity) { + return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f)); + } + /** * Calculate the amount of overshoot using an exponential falloff function with desired * properties, where the overshoot smoothly transitions at the 1.0f boundary into the @@ -208,4 +310,78 @@ public class Interpolators { path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f); return new PathInterpolator(path); } + + /** + * Returns a function that runs the given interpolator such that the entire progress is set + * between the given bounds. That is, we set the interpolation to 0 until lowerBound and reach + * 1 by upperBound. + */ + public static Interpolator clampToProgress(Interpolator interpolator, float lowerBound, + float upperBound) { + if (upperBound < lowerBound) { + throw new IllegalArgumentException( + String.format("upperBound (%f) must be greater than lowerBound (%f)", + upperBound, lowerBound)); + } + return t -> clampToProgress(interpolator, t, lowerBound, upperBound); + } + + /** + * Returns the progress value's progress between the lower and upper bounds. That is, the + * progress will be 0f from 0f to lowerBound, and reach 1f by upperBound. + * + * Between lowerBound and upperBound, the progress value will be interpolated using the provided + * interpolator. + */ + public static float clampToProgress( + Interpolator interpolator, float progress, float lowerBound, float upperBound) { + if (upperBound < lowerBound) { + throw new IllegalArgumentException( + String.format("upperBound (%f) must be greater than lowerBound (%f)", + upperBound, lowerBound)); + } + + if (progress == lowerBound && progress == upperBound) { + return progress == 0f ? 0 : 1; + } + if (progress < lowerBound) { + return 0; + } + if (progress > upperBound) { + return 1; + } + return interpolator.getInterpolation((progress - lowerBound) / (upperBound - lowerBound)); + } + + /** + * Returns the progress value's progress between the lower and upper bounds. That is, the + * progress will be 0f from 0f to lowerBound, and reach 1f by upperBound. + */ + public static float clampToProgress(float progress, float lowerBound, float upperBound) { + return clampToProgress(Interpolators.LINEAR, progress, lowerBound, upperBound); + } + + private static float mapRange(float value, float min, float max) { + return min + (value * (max - min)); + } + + /** + * Runs the given interpolator such that the interpolated value is mapped to the given range. + * This is useful, for example, if we only use this interpolator for part of the animation, + * such as to take over a user-controlled animation when they let go. + */ + public static Interpolator mapToProgress(Interpolator interpolator, float lowerBound, + float upperBound) { + return t -> mapRange(interpolator.getInterpolation(t), lowerBound, upperBound); + } + + /** + * Returns the reverse of the provided interpolator, following the formula: g(x) = 1 - f(1 - x). + * In practice, this means that if f is an interpolator used to model a value animating between + * m and n, g is the interpolator to use to obtain the specular behavior when animating from n + * to m. + */ + public static Interpolator reverse(Interpolator interpolator) { + return t -> 1 - interpolator.getInterpolation(1 - t); + } }
\ No newline at end of file diff --git a/animationlib/src/com/android/app/animation/InterpolatorsAndroidX.java b/animationlib/src/com/android/app/animation/InterpolatorsAndroidX.java index 7142f54..2ace0a3 100644 --- a/animationlib/src/com/android/app/animation/InterpolatorsAndroidX.java +++ b/animationlib/src/com/android/app/animation/InterpolatorsAndroidX.java @@ -24,6 +24,7 @@ import androidx.core.animation.BounceInterpolator; import androidx.core.animation.DecelerateInterpolator; import androidx.core.animation.Interpolator; import androidx.core.animation.LinearInterpolator; +import androidx.core.animation.OvershootInterpolator; import androidx.core.animation.PathInterpolator; /** @@ -65,6 +66,25 @@ public class InterpolatorsAndroidX { public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator( 0.05f, 0.7f, 0.1f, 1f); + public static final Interpolator EXAGGERATED_EASE; + static { + Path exaggeratedEase = new Path(); + exaggeratedEase.moveTo(0, 0); + exaggeratedEase.cubicTo(0.05f, 0f, 0.133333f, 0.08f, 0.166666f, 0.4f); + exaggeratedEase.cubicTo(0.225f, 0.94f, 0.5f, 1f, 1f, 1f); + EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase); + } + + public static final Interpolator INSTANT = t -> 1; + /** + * All values of t map to 0 until t == 1. This is primarily useful for setting view visibility, + * which should only happen at the very end of the animation (when it's already hidden). + */ + public static final Interpolator FINAL_FRAME = t -> t < 1 ? 0 : 1; + + public static final Interpolator OVERSHOOT_0_75 = new OvershootInterpolator(0.75f); + public static final Interpolator OVERSHOOT_1_2 = new OvershootInterpolator(1.2f); + public static final Interpolator OVERSHOOT_1_7 = new OvershootInterpolator(1.7f); /* * ============================================================================================ @@ -138,11 +158,27 @@ public class InterpolatorsAndroidX { public static final Interpolator FAST_OUT_SLOW_IN_REVERSE = new PathInterpolator(0.8f, 0f, 0.6f, 1f); public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f); + public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f); + public static final Interpolator AGGRESSIVE_EASE_IN_OUT = new PathInterpolator(0.6f,0, 0.4f, 1); + + public static final Interpolator DECELERATED_EASE = new PathInterpolator(0, 0, .2f, 1f); + public static final Interpolator ACCELERATED_EASE = new PathInterpolator(0.4f, 0, 1f, 1f); + public static final Interpolator PREDICTIVE_BACK_DECELERATED_EASE = + new PathInterpolator(0, 0, 0, 1f); public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f); public static final Interpolator ACCELERATE = new AccelerateInterpolator(); + public static final Interpolator ACCELERATE_0_5 = new AccelerateInterpolator(0.5f); + public static final Interpolator ACCELERATE_0_75 = new AccelerateInterpolator(0.75f); + public static final Interpolator ACCELERATE_1_5 = new AccelerateInterpolator(1.5f); + public static final Interpolator ACCELERATE_2 = new AccelerateInterpolator(2); public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator(); + public static final Interpolator DECELERATE = new DecelerateInterpolator(); + public static final Interpolator DECELERATE_1_5 = new DecelerateInterpolator(1.5f); + public static final Interpolator DECELERATE_1_7 = new DecelerateInterpolator(1.7f); + public static final Interpolator DECELERATE_2 = new DecelerateInterpolator(2); public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f); + public static final Interpolator DECELERATE_3 = new DecelerateInterpolator(3f); public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f); public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f, @@ -169,12 +205,77 @@ public class InterpolatorsAndroidX { public static final Interpolator TOUCH_RESPONSE_REVERSE = new PathInterpolator(0.9f, 0f, 0.7f, 1f); + public static final Interpolator TOUCH_RESPONSE_ACCEL_DEACCEL = + v -> ACCELERATE_DECELERATE.getInterpolation(TOUCH_RESPONSE.getInterpolation(v)); + + + /** + * Inversion of ZOOM_OUT, compounded with an ease-out. + */ + public static final Interpolator ZOOM_IN = new Interpolator() { + @Override + public float getInterpolation(float v) { + return DECELERATE_3.getInterpolation(1 - ZOOM_OUT.getInterpolation(1 - v)); + } + }; + + public static final Interpolator ZOOM_OUT = new Interpolator() { + + private static final float FOCAL_LENGTH = 0.35f; + + @Override + public float getInterpolation(float v) { + return zInterpolate(v); + } + + /** + * This interpolator emulates the rate at which the perceived scale of an object changes + * as its distance from a camera increases. When this interpolator is applied to a scale + * animation on a view, it evokes the sense that the object is shrinking due to moving away + * from the camera. + */ + private float zInterpolate(float input) { + return (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + input)) / + (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + 1.0f)); + } + }; + + public static final Interpolator SCROLL = new Interpolator() { + @Override + public float getInterpolation(float t) { + t -= 1.0f; + return t*t*t*t*t + 1; + } + }; + + public static final Interpolator SCROLL_CUBIC = new Interpolator() { + @Override + public float getInterpolation(float t) { + t -= 1.0f; + return t*t*t + 1; + } + }; + + private static final float FAST_FLING_PX_MS = 10; + /* * ============================================================================================ * Functions / Utilities * ============================================================================================ */ + public static Interpolator scrollInterpolatorForVelocity(float velocity) { + return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC; + } + + /** + * Create an OvershootInterpolator with tension directly related to the velocity (in px/ms). + * @param velocity The start velocity of the animation we want to overshoot. + */ + public static Interpolator overshootInterpolatorForVelocity(float velocity) { + return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f)); + } + /** * Calculate the amount of overshoot using an exponential falloff function with desired * properties, where the overshoot smoothly transitions at the 1.0f boundary into the @@ -215,4 +316,78 @@ public class InterpolatorsAndroidX { path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f); return new PathInterpolator(path); } + + /** + * Returns a function that runs the given interpolator such that the entire progress is set + * between the given bounds. That is, we set the interpolation to 0 until lowerBound and reach + * 1 by upperBound. + */ + public static Interpolator clampToProgress(Interpolator interpolator, float lowerBound, + float upperBound) { + if (upperBound < lowerBound) { + throw new IllegalArgumentException( + String.format("upperBound (%f) must be greater than lowerBound (%f)", + upperBound, lowerBound)); + } + return t -> clampToProgress(interpolator, t, lowerBound, upperBound); + } + + /** + * Returns the progress value's progress between the lower and upper bounds. That is, the + * progress will be 0f from 0f to lowerBound, and reach 1f by upperBound. + * + * Between lowerBound and upperBound, the progress value will be interpolated using the provided + * interpolator. + */ + public static float clampToProgress( + Interpolator interpolator, float progress, float lowerBound, float upperBound) { + if (upperBound < lowerBound) { + throw new IllegalArgumentException( + String.format("upperBound (%f) must be greater than lowerBound (%f)", + upperBound, lowerBound)); + } + + if (progress == lowerBound && progress == upperBound) { + return progress == 0f ? 0 : 1; + } + if (progress < lowerBound) { + return 0; + } + if (progress > upperBound) { + return 1; + } + return interpolator.getInterpolation((progress - lowerBound) / (upperBound - lowerBound)); + } + + /** + * Returns the progress value's progress between the lower and upper bounds. That is, the + * progress will be 0f from 0f to lowerBound, and reach 1f by upperBound. + */ + public static float clampToProgress(float progress, float lowerBound, float upperBound) { + return clampToProgress(LINEAR, progress, lowerBound, upperBound); + } + + private static float mapRange(float value, float min, float max) { + return min + (value * (max - min)); + } + + /** + * Runs the given interpolator such that the interpolated value is mapped to the given range. + * This is useful, for example, if we only use this interpolator for part of the animation, + * such as to take over a user-controlled animation when they let go. + */ + public static Interpolator mapToProgress(Interpolator interpolator, float lowerBound, + float upperBound) { + return t -> mapRange(interpolator.getInterpolation(t), lowerBound, upperBound); + } + + /** + * Returns the reverse of the provided interpolator, following the formula: g(x) = 1 - f(1 - x). + * In practice, this means that if f is an interpolator used to model a value animating between + * m and n, g is the interpolator to use to obtain the specular behavior when animating from n + * to m. + */ + public static Interpolator reverse(Interpolator interpolator) { + return t -> 1 - interpolator.getInterpolation(1 - t); + } }
\ No newline at end of file diff --git a/animationlib/tests/src/com/android/app/animation/InterpolatorResourcesTest.kt b/animationlib/tests/src/com/android/app/animation/InterpolatorResourcesTest.kt new file mode 100644 index 0000000..ed4670e --- /dev/null +++ b/animationlib/tests/src/com/android/app/animation/InterpolatorResourcesTest.kt @@ -0,0 +1,64 @@ +package com.android.app.animation + +import android.annotation.InterpolatorRes +import android.content.Context +import android.view.animation.AnimationUtils +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class InterpolatorResourcesTest { + + private lateinit var context: Context + + @Before + fun setup() { + context = InstrumentationRegistry.getInstrumentation().context + } + + @Test + fun testResourceInterpolatorsMatchCodeInterpolators() { + var progress = 0f + while (progress < +1f) { + assertEquals( + InterpolatorsAndroidX.EMPHASIZED.getInterpolation(progress), + loadInterpolator(R.interpolator.emphasized_interpolator).getInterpolation(progress) + ) + assertEquals( + InterpolatorsAndroidX.EMPHASIZED_ACCELERATE.getInterpolation(progress), + loadInterpolator(R.interpolator.emphasized_accelerate_interpolator) + .getInterpolation(progress) + ) + assertEquals( + InterpolatorsAndroidX.EMPHASIZED_DECELERATE.getInterpolation(progress), + loadInterpolator(R.interpolator.emphasized_decelerate_interpolator) + .getInterpolation(progress) + ) + assertEquals( + InterpolatorsAndroidX.STANDARD.getInterpolation(progress), + loadInterpolator(R.interpolator.standard_interpolator).getInterpolation(progress) + ) + assertEquals( + InterpolatorsAndroidX.STANDARD_ACCELERATE.getInterpolation(progress), + loadInterpolator(R.interpolator.standard_accelerate_interpolator) + .getInterpolation(progress) + ) + assertEquals( + InterpolatorsAndroidX.STANDARD_DECELERATE.getInterpolation(progress), + loadInterpolator(R.interpolator.standard_decelerate_interpolator) + .getInterpolation(progress) + ) + progress += 0.1f + } + } + + private fun loadInterpolator(@InterpolatorRes resourceInt: Int) = + AnimationUtils.loadInterpolator(context, resourceInt) + +} diff --git a/animationlib/tests/com/android/app/animation/InterpolatorsAndroidXTest.kt b/animationlib/tests/src/com/android/app/animation/InterpolatorsAndroidXTest.kt index 841e141..ffa706e 100644 --- a/animationlib/tests/com/android/app/animation/InterpolatorsAndroidXTest.kt +++ b/animationlib/tests/src/com/android/app/animation/InterpolatorsAndroidXTest.kt @@ -23,6 +23,9 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +private const val ANDROIDX_ANIM_PACKAGE_NAME = "androidx.core.animation." +private const val ANDROID_ANIM_PACKAGE_NAME = "android.view.animation." + @SmallTest @RunWith(JUnit4::class) class InterpolatorsAndroidXTest { @@ -46,7 +49,9 @@ class InterpolatorsAndroidXTest { private fun <T> Class<T>.getPublicMethods() = declaredMethods .filter { Modifier.isPublic(it.modifiers) } - .map { it.toString().replace(name, "") } + .map { it.toString().replace(name, "") + .replace(ANDROIDX_ANIM_PACKAGE_NAME, "") + .replace(ANDROID_ANIM_PACKAGE_NAME, "") } .toSet() private fun <T> Class<T>.getPublicFields() = diff --git a/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java index 513a75d..0ee9db9 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java +++ b/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java @@ -34,16 +34,21 @@ import android.util.FloatProperty; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.core.graphics.ColorUtils; public class FastBitmapDrawable extends Drawable implements Drawable.Callback { private static final Interpolator ACCEL = new AccelerateInterpolator(); private static final Interpolator DEACCEL = new DecelerateInterpolator(); + private static final Interpolator HOVER_EMPHASIZED_DECELERATE_INTERPOLATOR = + new PathInterpolator(0.05f, 0.7f, 0.1f, 1.0f); - private static final float PRESSED_SCALE = 1.1f; + @VisibleForTesting protected static final float PRESSED_SCALE = 1.1f; + @VisibleForTesting protected static final float HOVERED_SCALE = 1.1f; public static final int WHITE_SCRIM_ALPHA = 138; private static final float DISABLED_DESATURATION = 1f; @@ -51,6 +56,9 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback { protected static final int FULLY_OPAQUE = 255; public static final int CLICK_FEEDBACK_DURATION = 200; + public static final int HOVER_FEEDBACK_DURATION = 300; + + private static boolean sFlagHoverEnabled = false; protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); protected final Bitmap mBitmap; @@ -58,12 +66,13 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback { @Nullable private ColorFilter mColorFilter; - private boolean mIsPressed; + @VisibleForTesting protected boolean mIsPressed; + @VisibleForTesting protected boolean mIsHovered; protected boolean mIsDisabled; float mDisabledAlpha = 1f; // Animator and properties for the fast bitmap drawable's scale - private static final FloatProperty<FastBitmapDrawable> SCALE + @VisibleForTesting protected static final FloatProperty<FastBitmapDrawable> SCALE = new FloatProperty<FastBitmapDrawable>("scale") { @Override public Float get(FastBitmapDrawable fastBitmapDrawable) { @@ -76,7 +85,7 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback { fastBitmapDrawable.invalidateSelf(); } }; - private ObjectAnimator mScaleAnimation; + @VisibleForTesting protected ObjectAnimator mScaleAnimation; private float mScale = 1; private int mAlpha = 255; @@ -163,6 +172,9 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback { mAlpha = alpha; mPaint.setAlpha(alpha); invalidateSelf(); + if (mBadge != null) { + mBadge.setAlpha(alpha); + } } } @@ -223,37 +235,41 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback { @Override protected boolean onStateChange(int[] state) { boolean isPressed = false; + boolean isHovered = false; for (int s : state) { if (s == android.R.attr.state_pressed) { isPressed = true; break; + } else if (sFlagHoverEnabled && s == android.R.attr.state_hovered) { + isHovered = true; + // Do not break on hovered state, as pressed state should take precedence. } } - if (mIsPressed != isPressed) { - mIsPressed = isPressed; - + if (mIsPressed != isPressed || mIsHovered != isHovered) { if (mScaleAnimation != null) { mScaleAnimation.cancel(); - mScaleAnimation = null; } - if (mIsPressed) { - // Animate when going to pressed state - mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE); - mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION); - mScaleAnimation.setInterpolator(ACCEL); - mScaleAnimation.start(); - } else { + float endScale = isPressed ? PRESSED_SCALE : (isHovered ? HOVERED_SCALE : 1f); + if (mScale != endScale) { if (isVisible()) { - mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, 1f); - mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION); - mScaleAnimation.setInterpolator(DEACCEL); + Interpolator interpolator = + isPressed != mIsPressed ? (isPressed ? ACCEL : DEACCEL) + : HOVER_EMPHASIZED_DECELERATE_INTERPOLATOR; + int duration = + isPressed != mIsPressed ? CLICK_FEEDBACK_DURATION + : HOVER_FEEDBACK_DURATION; + mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, endScale); + mScaleAnimation.setDuration(duration); + mScaleAnimation.setInterpolator(interpolator); mScaleAnimation.start(); } else { - mScale = 1f; + mScale = endScale; invalidateSelf(); } } + mIsPressed = isPressed; + mIsHovered = isHovered; return true; } return false; @@ -366,6 +382,13 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback { unscheduleSelf(what); } + /** + * Sets whether hover state functionality is enabled. + */ + public static void setFlagHoverEnabled(boolean isFlagHoverEnabled) { + sFlagHoverEnabled = isFlagHoverEnabled; + } + protected static class FastBitmapConstantState extends ConstantState { protected final Bitmap mBitmap; protected final int mIconColor; diff --git a/viewcapturelib/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt b/viewcapturelib/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt index 8a3cf1c..9a857c3 100644 --- a/viewcapturelib/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt +++ b/viewcapturelib/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt @@ -25,7 +25,6 @@ import android.os.ParcelFileDescriptor import android.os.Process import android.provider.Settings import android.util.Log -import android.view.Choreographer import android.window.IDumpCallback import androidx.annotation.AnyThread import androidx.annotation.VisibleForTesting @@ -40,8 +39,8 @@ private val TAG = SettingsAwareViewCapture::class.java.simpleName */ class SettingsAwareViewCapture @VisibleForTesting -internal constructor(private val context: Context, choreographer: Choreographer, executor: Executor) - : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, choreographer, executor) { +internal constructor(private val context: Context, executor: Executor) + : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, executor) { /** Dumps all the active view captures to the wm trace directory via LauncherAppService */ private val mDumpCallback: IDumpCallback.Stub = object : IDumpCallback.Stub() { override fun onDump(out: ParcelFileDescriptor) { @@ -91,7 +90,7 @@ internal constructor(private val context: Context, choreographer: Choreographer, fun getInstance(context: Context): ViewCapture = when { INSTANCE != null -> INSTANCE!! Looper.myLooper() == Looper.getMainLooper() -> SettingsAwareViewCapture( - context.applicationContext, Choreographer.getInstance(), + context.applicationContext, createAndStartNewLooperExecutor("SAViewCapture", Process.THREAD_PRIORITY_FOREGROUND)).also { INSTANCE = it } else -> try { diff --git a/viewcapturelib/src/com/android/app/viewcapture/SimpleViewCapture.kt b/viewcapturelib/src/com/android/app/viewcapture/SimpleViewCapture.kt index 2773f6b..420faca 100644 --- a/viewcapturelib/src/com/android/app/viewcapture/SimpleViewCapture.kt +++ b/viewcapturelib/src/com/android/app/viewcapture/SimpleViewCapture.kt @@ -1,8 +1,6 @@ package com.android.app.viewcapture import android.os.Process -import android.view.Choreographer open class SimpleViewCapture(threadName: String) : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, - MAIN_EXECUTOR.submit { Choreographer.getInstance() }.get(), createAndStartNewLooperExecutor(threadName, Process.THREAD_PRIORITY_FOREGROUND))
\ No newline at end of file diff --git a/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java b/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java index fb5abd6..bbd797e 100644 --- a/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java +++ b/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java @@ -16,15 +16,18 @@ package com.android.app.viewcapture; +import static com.android.app.viewcapture.data.ExportedData.MagicNumber.MAGIC_NUMBER_H; +import static com.android.app.viewcapture.data.ExportedData.MagicNumber.MAGIC_NUMBER_L; + import android.content.Context; import android.content.res.Resources; import android.media.permission.SafeCloseable; import android.os.HandlerThread; import android.os.Looper; +import android.os.SystemClock; import android.os.Trace; import android.text.TextUtils; import android.util.SparseArray; -import android.view.Choreographer; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -51,6 +54,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Predicate; @@ -65,6 +69,9 @@ public abstract class ViewCapture { private static final int PFLAG_INVALIDATED = 0x80000000; private static final int PFLAG_DIRTY_MASK = 0x00200000; + private static final long MAGIC_NUMBER_FOR_WINSCOPE = + ((long) MAGIC_NUMBER_H.getNumber() << 32) | MAGIC_NUMBER_L.getNumber(); + // Number of frames to keep in memory private final int mMemorySize; protected static final int DEFAULT_MEMORY_SIZE = 2000; @@ -77,16 +84,13 @@ public abstract class ViewCapture { private final List<WindowListener> mListeners = new ArrayList<>(); protected final Executor mBgExecutor; - private final Choreographer mChoreographer; // Pool used for capturing view tree on the UI thread. private ViewRef mPool = new ViewRef(); private boolean mIsEnabled = true; - protected ViewCapture(int memorySize, int initPoolSize, Choreographer choreographer, - Executor bgExecutor) { + protected ViewCapture(int memorySize, int initPoolSize, Executor bgExecutor) { mMemorySize = memorySize; - mChoreographer = choreographer; mBgExecutor = bgExecutor; mBgExecutor.execute(() -> initPool(initPoolSize)); } @@ -171,16 +175,21 @@ public abstract class ViewCapture { @AnyThread public void dumpTo(OutputStream os, Context context) throws InterruptedException, ExecutionException, IOException { - if (!mIsEnabled) { - return; - } + if (mIsEnabled) getExportedData(context).writeTo(os); + } + + @VisibleForTesting + public ExportedData getExportedData(Context context) + throws InterruptedException, ExecutionException { ArrayList<Class> classList = new ArrayList<>(); - ExportedData.newBuilder() + return ExportedData.newBuilder() + .setMagicNumber(MAGIC_NUMBER_FOR_WINSCOPE) .setPackage(context.getPackageName()) .addAllWindowData(getWindowData(context, classList, l -> l.mIsActive).get()) .addAllClassname(toStringList(classList)) - .build() - .writeTo(os); + .setRealToElapsedTimeOffsetNanos(TimeUnit.MILLISECONDS + .toNanos(System.currentTimeMillis()) - SystemClock.elapsedRealtimeNanos()) + .build(); } private static List<String> toStringList(List<Class> classList) { @@ -280,7 +289,7 @@ public abstract class ViewCapture { ViewRef captured = mViewRef.next; if (captured != null) { captured.callback = mCaptureCallback; - captured.choreographerTimeNanos = mChoreographer.getFrameTimeNanos(); + captured.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); mBgExecutor.execute(captured); } mIsFirstFrame = false; @@ -293,12 +302,12 @@ public abstract class ViewCapture { */ @WorkerThread private void captureViewPropertiesBg(ViewRef viewRefStart) { - long choreographerTimeNanos = viewRefStart.choreographerTimeNanos; + long elapsedRealtimeNanos = viewRefStart.elapsedRealtimeNanos; mFrameIndexBg++; if (mFrameIndexBg >= mMemorySize) { mFrameIndexBg = 0; } - mFrameTimesNanosBg[mFrameIndexBg] = choreographerTimeNanos; + mFrameTimesNanosBg[mFrameIndexBg] = elapsedRealtimeNanos; ViewPropertyRef recycle = mNodesBg[mFrameIndexBg]; @@ -546,7 +555,7 @@ public abstract class ViewCapture { public ViewRef next; public Consumer<ViewRef> callback = null; - public long choreographerTimeNanos = 0; + public long elapsedRealtimeNanos = 0; public void transferTo(ViewPropertyRef out) { out.childCount = this.childCount; diff --git a/viewcapturelib/src/com/android/app/viewcapture/proto/view_capture.proto b/viewcapturelib/src/com/android/app/viewcapture/proto/view_capture.proto index d4df2ae..c73ce8b 100644 --- a/viewcapturelib/src/com/android/app/viewcapture/proto/view_capture.proto +++ b/viewcapturelib/src/com/android/app/viewcapture/proto/view_capture.proto @@ -21,9 +21,23 @@ package com.android.app.viewcapture.data; option java_multiple_files = true; message ExportedData { - repeated WindowData windowData = 1; - optional string package = 2; - repeated string classname = 3; + /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L + (this is needed because enums have to be 32 bits and there's no nice way to put 64bit + constants into .proto files. */ + enum MagicNumber { + INVALID = 0; + MAGIC_NUMBER_L = 0x65906578; /* AZAN (ASCII) */ + MAGIC_NUMBER_H = 0x68658273; /* DARI (ASCII) */ + } + + optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ + repeated WindowData windowData = 2; + optional string package = 3; + repeated string classname = 4; + + /* offset between real-time clock and elapsed time clock in nanoseconds. + Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */ + optional fixed64 real_to_elapsed_time_offset_nanos = 5; } message WindowData { @@ -37,7 +51,7 @@ message MotionWindowData { } message FrameData { - optional int64 timestamp = 1; // choreographer timestamp in nanoseconds + optional int64 timestamp = 1; // unit is elapsed realtime nanos optional ViewNode node = 2; } diff --git a/viewcapturelib/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt b/viewcapturelib/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt index 49d50bf..15352aa 100644 --- a/viewcapturelib/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt +++ b/viewcapturelib/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt @@ -50,8 +50,7 @@ class SettingsAwareViewCaptureTest { Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 0) activityScenarioRule.scenario.onActivity { activity -> - val viewCapture: ViewCapture = - SettingsAwareViewCapture(context, Choreographer.getInstance(), MAIN_EXECUTOR) + val viewCapture: ViewCapture = SettingsAwareViewCapture(context, MAIN_EXECUTOR) val rootView: View = activity.requireViewById(android.R.id.content) val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") @@ -77,8 +76,7 @@ class SettingsAwareViewCaptureTest { Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 1) activityScenarioRule.scenario.onActivity { activity -> - val viewCapture: ViewCapture = - SettingsAwareViewCapture(context, Choreographer.getInstance(), MAIN_EXECUTOR) + val viewCapture: ViewCapture = SettingsAwareViewCapture(context, MAIN_EXECUTOR) val rootView: View = activity.requireViewById(android.R.id.content) val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") diff --git a/viewcapturelib/tests/com/android/app/viewcapture/ViewCaptureTest.kt b/viewcapturelib/tests/com/android/app/viewcapture/ViewCaptureTest.kt index c1873a6..e3272c4 100644 --- a/viewcapturelib/tests/com/android/app/viewcapture/ViewCaptureTest.kt +++ b/viewcapturelib/tests/com/android/app/viewcapture/ViewCaptureTest.kt @@ -19,7 +19,6 @@ package com.android.app.viewcapture import android.content.Intent import android.media.permission.SafeCloseable import android.testing.AndroidTestingRunner -import android.view.Choreographer import android.view.View import android.widget.LinearLayout import android.widget.TextView @@ -41,7 +40,7 @@ class ViewCaptureTest { private val initPoolSize = 15 private val viewCapture by lazy { object : - ViewCapture(memorySize, initPoolSize, Choreographer.getInstance(), MAIN_EXECUTOR) {} + ViewCapture(memorySize, initPoolSize, MAIN_EXECUTOR) {} } private val activityIntent = @@ -52,33 +51,29 @@ class ViewCaptureTest { @Test fun testWindowListenerDumpsOneFrameAfterInvalidate() { activityScenarioRule.scenario.onActivity { activity -> - Choreographer.getInstance().postFrameCallback { - val closeable = startViewCaptureAndInvalidateNTimes(1, activity) - val rootView = activity.requireViewById<View>(android.R.id.content) - val data = viewCapture.getDumpTask(rootView).get().get() + val closeable = startViewCaptureAndInvalidateNTimes(1, activity) + val rootView = activity.requireViewById<View>(android.R.id.content) + val data = viewCapture.getDumpTask(rootView).get().get() - assertEquals(1, data.frameDataList.size) - verifyTestActivityViewHierarchy(data) - closeable.close() - } + assertEquals(1, data.frameDataList.size) + verifyTestActivityViewHierarchy(data) + closeable.close() } } @Test fun testWindowListenerDumpsCorrectlyAfterRecyclingStarted() { activityScenarioRule.scenario.onActivity { activity -> - Choreographer.getInstance().postFrameCallback { - val closeable = startViewCaptureAndInvalidateNTimes(memorySize + 5, activity) - val rootView = activity.requireViewById<View>(android.R.id.content) - val data = viewCapture.getDumpTask(rootView).get().get() + val closeable = startViewCaptureAndInvalidateNTimes(memorySize + 5, activity) + val rootView = activity.requireViewById<View>(android.R.id.content) + val data = viewCapture.getDumpTask(rootView).get().get() - // since ViewCapture MEMORY_SIZE is [viewCaptureMemorySize], only - // [viewCaptureMemorySize] frames are exported, although the view is invalidated - // [viewCaptureMemorySize + 5] times - assertEquals(memorySize, data.frameDataList.size) - verifyTestActivityViewHierarchy(data) - closeable.close() - } + // since ViewCapture MEMORY_SIZE is [viewCaptureMemorySize], only + // [viewCaptureMemorySize] frames are exported, although the view is invalidated + // [viewCaptureMemorySize + 5] times + assertEquals(memorySize, data.frameDataList.size) + verifyTestActivityViewHierarchy(data) + closeable.close() } } |