Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0104e414 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Automatically detect when Views move and apply velocity." into main

parents cef6c818 20c76ddc
Loading
Loading
Loading
Loading
+37 −2
Original line number Diff line number Diff line
@@ -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.
+24 −4
Original line number Diff line number Diff line
@@ -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;
@@ -7568,7 +7569,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);
@@ -12395,6 +12397,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 {
@@ -12420,9 +12433,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
@@ -12527,6 +12539,14 @@ public final class ViewRootImpl implements ViewParent,
        return mPreferredFrameRate >= 0 ? mPreferredFrameRate : mLastPreferredFrameRate;
    }
    /**
     * Returns whether touch boost is currently enabled.
     */
    @VisibleForTesting
    public boolean getIsTouchBoosting() {
        return mIsTouchBoosting;
    }
    /**
     * Get the value of mFrameRateCompatibility
     */
+25 −0
Original line number Diff line number Diff line
<?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>
+102 −0
Original line number Diff line number Diff line
/*
 * 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 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 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
    public void frameRateChangesWhenContentMoves() {
        mMovingView.offsetLeftAndRight(100);
        float frameRate = mViewRoot.getPreferredFrameRate();
        assertTrue(frameRate > 0);
    }

    @UiThreadTest
    @Test
    public void firstFrameNoMovement() {
        assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
    }

    @Test
    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());
        });
    }
}