summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2023-12-08 13:14:14 -0800
committerXin Li <delphij@google.com>2023-12-08 13:14:14 -0800
commit48810913bd14f1b6215b053797d92bf0f6e59017 (patch)
tree4001f5a3a7a2efeee47f63741f610d06ab4036c6
parent65bef66686f9ea478626d815d565653cb8802813 (diff)
parent30a2b10bc538eb43fef45dd97eb74bf52589cd4f (diff)
downloadsystemui-48810913bd14f1b6215b053797d92bf0f6e59017.tar.gz
Merge Android 14 QPR1
Merged-In: I4762502d8982a4fb1cc143f8c332010b5afe6e96 Bug: 315507370 Change-Id: I12455b9a319d7162ead5edab61433f62314cab05
-rw-r--r--animationlib/Android.bp3
-rw-r--r--animationlib/build.gradle4
-rw-r--r--animationlib/res/interpolator/emphasized_accelerate_interpolator.xml22
-rw-r--r--animationlib/res/interpolator/emphasized_decelerate_interpolator.xml22
-rw-r--r--animationlib/res/interpolator/emphasized_interpolator.xml19
-rw-r--r--animationlib/res/interpolator/standard_accelerate_interpolator.xml22
-rw-r--r--animationlib/res/interpolator/standard_decelerate_interpolator.xml22
-rw-r--r--animationlib/res/interpolator/standard_interpolator.xml22
-rw-r--r--animationlib/src/com/android/app/animation/Interpolators.java176
-rw-r--r--animationlib/src/com/android/app/animation/InterpolatorsAndroidX.java175
-rw-r--r--animationlib/tests/src/com/android/app/animation/InterpolatorResourcesTest.kt64
-rw-r--r--animationlib/tests/src/com/android/app/animation/InterpolatorsAndroidXTest.kt (renamed from animationlib/tests/com/android/app/animation/InterpolatorsAndroidXTest.kt)7
-rw-r--r--iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java61
-rw-r--r--viewcapturelib/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt7
-rw-r--r--viewcapturelib/src/com/android/app/viewcapture/SimpleViewCapture.kt2
-rw-r--r--viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java39
-rw-r--r--viewcapturelib/src/com/android/app/viewcapture/proto/view_capture.proto22
-rw-r--r--viewcapturelib/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt6
-rw-r--r--viewcapturelib/tests/com/android/app/viewcapture/ViewCaptureTest.kt37
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()
}
}