summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2024-03-07 04:41:25 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-03-07 04:41:25 +0000
commit73dc5d51316a05cc55db0fc8051a70c555120525 (patch)
treedb8a2bd099a0eeb6fd4ad0e159f705c1649d7289
parentdf3b643473db6607bc9c08275b0fc9fbd84593db (diff)
parent7d68d625c16614d6b00e3218c165d685a7506d9d (diff)
Merge "Automatically detect when Views move and apply velocity." into main
-rw-r--r--core/java/android/view/View.java41
-rw-r--r--core/java/android/view/ViewRootImpl.java28
-rw-r--r--core/tests/coretests/res/layout/view_velocity_test.xml25
-rw-r--r--core/tests/coretests/src/android/view/ViewVelocityTest.java108
4 files changed, 195 insertions, 7 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 828004b6b235..a6f380d4d483 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -25,6 +25,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
@@ -5629,7 +5630,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@Nullable
private ViewTranslationCallback mViewTranslationCallback;
- private float mFrameContentVelocity = 0;
+ private float mFrameContentVelocity = -1;
@Nullable
@@ -5660,6 +5661,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
protected long mMinusTwoFrameIntervalMillis = 0;
private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ private float mLastFrameX = Float.NaN;
+ private float mLastFrameY = Float.NaN;
+
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -24597,7 +24601,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public void draw(@NonNull Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
- mFrameContentVelocity = 0;
+
+ mFrameContentVelocity = -1;
+ mLastFrameX = mLeft + mRenderNode.getTranslationX();
+ mLastFrameY = mTop + mRenderNode.getTranslationY();
/*
* Draw traversal performs several drawing steps which must be executed
@@ -33673,6 +33680,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
viewRootImpl.recordViewPercentage(sizePercentage);
}
+ if (viewVelocityApi()) {
+ float velocity = mFrameContentVelocity;
+ if (velocity < 0f) {
+ velocity = calculateVelocity();
+ }
+ if (velocity > 0f) {
+ float frameRate = convertVelocityToFrameRate(velocity);
+ viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE);
+ return;
+ }
+ }
if (!Float.isNaN(mPreferredFrameRate)) {
if (mPreferredFrameRate < 0) {
if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
@@ -33695,6 +33713,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ private float convertVelocityToFrameRate(float velocityPps) {
+ float density = getResources().getDisplayMetrics().density;
+ float velocityDps = velocityPps / density;
+ // Choose a frame rate in increments of 10fps
+ return Math.min(140f, 60f + (10f * (float) Math.floor(velocityDps / 300f)));
+ }
+
+ private float calculateVelocity() {
+ // This current calculation is very simple. If something on the screen moved, then
+ // it votes for the highest velocity. If it doesn't move, then return 0.
+ float x = mLeft + mRenderNode.getTranslationX();
+ float y = mTop + mRenderNode.getTranslationY();
+
+ return (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY))
+ ? 100_000f : 0f;
+ }
+
/**
* Set the current velocity of the View, we only track positive value.
* We will use the velocity information to adjust the frame rate when applicable.
@@ -33725,7 +33760,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@FlaggedApi(FLAG_VIEW_VELOCITY_API)
public float getFrameContentVelocity() {
if (viewVelocityApi()) {
- return mFrameContentVelocity;
+ return (mFrameContentVelocity < 0f) ? 0f : mFrameContentVelocity;
}
return 0;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 02f8e6e9b810..42f64052d987 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -31,6 +31,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -7571,7 +7572,8 @@ public final class ViewRootImpl implements ViewParent,
}
// For the variable refresh rate project
- if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
+ if (handled && shouldTouchBoost(action & MotionEvent.ACTION_MASK,
+ mWindowAttributes.type)) {
// set the frame rate to the maximum value.
mIsTouchBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
@@ -12398,6 +12400,17 @@ public final class ViewRootImpl implements ViewParent,
mFrameRateCompatibility).applyAsyncUnsafe();
mLastPreferredFrameRate = preferredFrameRate;
}
+ if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) {
+ // We've received a velocity, so we'll let the velocity control the
+ // frame rate unless we receive additional motion events.
+ mIsTouchBoosting = false;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(
+ Trace.TRACE_TAG_VIEW,
+ "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame"
+ );
+ }
+ }
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate", e);
} finally {
@@ -12423,9 +12436,8 @@ public final class ViewRootImpl implements ViewParent,
}
private boolean shouldTouchBoost(int motionEventAction, int windowType) {
- boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
- || motionEventAction == MotionEvent.ACTION_MOVE
- || motionEventAction == MotionEvent.ACTION_UP;
+ // boost for almost all input
+ boolean desiredAction = motionEventAction != MotionEvent.ACTION_OUTSIDE;
boolean undesiredType = windowType == TYPE_INPUT_METHOD
&& sToolkitFrameRateTypingReadOnlyFlagValue;
// use toolkitSetFrameRate flag to gate the change
@@ -12531,6 +12543,14 @@ public final class ViewRootImpl implements ViewParent,
}
/**
+ * Returns whether touch boost is currently enabled.
+ */
+ @VisibleForTesting
+ public boolean getIsTouchBoosting() {
+ return mIsTouchBoosting;
+ }
+
+ /**
* Get the value of mFrameRateCompatibility
*/
@VisibleForTesting
diff --git a/core/tests/coretests/res/layout/view_velocity_test.xml b/core/tests/coretests/res/layout/view_velocity_test.xml
new file mode 100644
index 000000000000..98154a4b6b78
--- /dev/null
+++ b/core/tests/coretests/res/layout/view_velocity_test.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/frameLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <View
+ android:id="@+id/moving_view"
+ android:layout_width="50dp"
+ android:layout_height="50dp" />
+</FrameLayout>
diff --git a/core/tests/coretests/src/android/view/ViewVelocityTest.java b/core/tests/coretests/src/android/view/ViewVelocityTest.java
new file mode 100644
index 000000000000..d437f7bc4060
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewVelocityTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package android.view;
+
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewVelocityTest {
+
+ @Rule
+ public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
+ ViewCaptureTestActivity.class);
+
+ private Activity mActivity;
+ private View mMovingView;
+ private ViewRootImpl mViewRoot;
+
+ @Before
+ public void setUp() throws Throwable {
+ mActivity = mActivityRule.getActivity();
+ mActivityRule.runOnUiThread(() -> {
+ mActivity.setContentView(R.layout.view_velocity_test);
+ mMovingView = mActivity.findViewById(R.id.moving_view);
+ });
+ ViewParent parent = mActivity.getWindow().getDecorView().getParent();
+ while (parent instanceof View) {
+ parent = parent.getParent();
+ }
+ mViewRoot = (ViewRootImpl) parent;
+ }
+
+ @UiThreadTest
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void frameRateChangesWhenContentMoves() {
+ mMovingView.offsetLeftAndRight(100);
+ float frameRate = mViewRoot.getPreferredFrameRate();
+ assertTrue(frameRate > 0);
+ }
+
+ @UiThreadTest
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void firstFrameNoMovement() {
+ assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void touchBoostDisable() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent down = MotionEvent.obtain(
+ /* downTime */ now,
+ /* eventTime */ now,
+ /* action */ MotionEvent.ACTION_DOWN,
+ /* x */ 0f,
+ /* y */ 0f,
+ /* metaState */ 0
+ );
+ mActivity.dispatchTouchEvent(down);
+ mMovingView.offsetLeftAndRight(10);
+ });
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ });
+
+ mActivityRule.runOnUiThread(() -> {
+ assertFalse(mViewRoot.getIsTouchBoosting());
+ });
+ }
+}