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

Commit 3ba91206 authored by Rachel Lee's avatar Rachel Lee
Browse files

Add SurfaceControl::setFrameRateSelectionStrategy

A new private API that allows a parent layer to override the frame rate
specifications of its descendants. The main use case is to allow the
display SurfaceControl to override descendants while animating.

Bug: 297418260
Test: atest CtsSurfaceControlTestsStaging
Change-Id: Ifc8737231b850b8cb02636b458cb154666a1c7ec
parent 1c504ed7
Loading
Loading
Loading
Loading
+56 −0
Original line number Diff line number Diff line
@@ -263,6 +263,8 @@ public final class SurfaceControl implements Parcelable {
            long nativeObject, int compatibility);
    private static native void nativeSetFrameRateCategory(
            long transactionObj, long nativeObject, int category);
    private static native void nativeSetFrameRateSelectionStrategy(
            long transactionObj, long nativeObject, int strategy);
    private static native long nativeGetHandle(long nativeObject);

    private static native void nativeSetFixedTransformHint(long transactionObj, long nativeObject,
@@ -850,6 +852,35 @@ public final class SurfaceControl implements Parcelable {
     */
    public static final int METADATA_GAME_MODE = 8;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = {"FRAME_RATE_SELECTION_STRATEGY_"},
            value = {FRAME_RATE_SELECTION_STRATEGY_SELF,
                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN})
    public @interface FrameRateSelectionStrategy {}

    // From window.h. Keep these in sync.
    /**
     * Default value. The layer uses its own frame rate specifications, assuming it has any
     * specifications, instead of its parent's.
     * However, {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} on an ancestor layer
     * supersedes this behavior, meaning that this layer will inherit the frame rate specifications
     * of that ancestor layer.
     * @hide
     */
    public static final int FRAME_RATE_SELECTION_STRATEGY_SELF = 0;

    /**
     * The layer's frame rate specifications will propagate to and override those of its descendant
     * layers.
     * The layer with this strategy has the {@link #FRAME_RATE_SELECTION_STRATEGY_SELF} behavior
     * for itself. This does mean that any parent or ancestor layer that also has the strategy
     * {@link FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} will override this layer's
     * frame rate specifications.
     * @hide
     */
    public static final int FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1;

    /**
     * Builder class for {@link SurfaceControl} objects.
     *
@@ -3668,6 +3699,31 @@ public final class SurfaceControl implements Parcelable {
            return this;
        }

        /**
         * Sets the frame rate selection strategy for the {@link SurfaceControl}.
         *
         * This instructs the system on how to choose a display refresh rate, following the
         * strategy for the layer's frame rate specifications relative to other layers'.
         *
         * @param sc The SurfaceControl to specify the frame rate category of.
         * @param strategy The frame rate selection strategy.
         *
         * @return This transaction object.
         *
         * @see #setFrameRate(SurfaceControl, float, int, int)
         * @see #setFrameRateCategory(SurfaceControl, int)
         * @see #setDefaultFrameRateCompatibility(SurfaceControl, int)
         *
         * @hide
         */
        @NonNull
        public Transaction setFrameRateSelectionStrategy(
                @NonNull SurfaceControl sc, @FrameRateSelectionStrategy int strategy) {
            checkPreconditions(sc);
            nativeSetFrameRateSelectionStrategy(mNativeObject, sc.mNativeObject, strategy);
            return this;
        }

        /**
         * Sets focus on the window identified by the input {@code token} if the window is focusable
         * otherwise the request is dropped.
+9 −0
Original line number Diff line number Diff line
@@ -967,6 +967,13 @@ static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transact
    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category));
}

static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj,
                                                jlong nativeObject, jint strategy) {
    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
    const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
    transaction->setFrameRateSelectionStrategy(ctrl, static_cast<int8_t>(strategy));
}

static void nativeSetFixedTransformHint(JNIEnv* env, jclass clazz, jlong transactionObj,
                                        jlong nativeObject, jint transformHint) {
    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2173,6 +2180,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
            (void*)nativeSetDefaultFrameRateCompatibility},
    {"nativeSetFrameRateCategory", "(JJI)V",
            (void*)nativeSetFrameRateCategory},
    {"nativeSetFrameRateSelectionStrategy", "(JJI)V",
            (void*)nativeSetFrameRateSelectionStrategy},
    {"nativeSetDisplaySurface", "(JLandroid/os/IBinder;J)V",
            (void*)nativeSetDisplaySurface },
    {"nativeSetDisplayLayerStack", "(JLandroid/os/IBinder;I)V",
+1 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ android_test {
        "androidx.test.rules",
        "compatibility-device-util-axt",
        "com.google.android.material_material",
        "SurfaceFlingerProperties",
        "truth-prebuilt",
    ],
    resource_dirs: ["src/main/res"],
+128 −7
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.sysprop.SurfaceFlingerProperties;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
@@ -45,6 +46,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * An Activity to help with frame rate testing.
@@ -217,6 +219,27 @@ public class GraphicsActivity extends Activity {
            postBuffer();
        }

        Surface getSurface() {
            return mSurface;
        }

        SurfaceControl getSurfaceControl() {
            return mSurfaceControl;
        }

        public int setFrameRate(float frameRate) {
            Log.i(TAG,
                    String.format("Setting frame rate for %s: frameRate=%.2f", mName, frameRate));

            int rc = 0;
            try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
                transaction.setFrameRate(
                        mSurfaceControl, frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
                transaction.apply();
            }
            return rc;
        }

        public int setFrameRateCategory(int category) {
            Log.i(TAG,
                    String.format(
@@ -230,6 +253,19 @@ public class GraphicsActivity extends Activity {
            return rc;
        }

        public int setFrameRateSelectionStrategy(int strategy) {
            Log.i(TAG,
                    String.format("Setting frame rate selection strategy for %s: strategy=%d",
                            mName, strategy));

            int rc = 0;
            try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
                transaction.setFrameRateSelectionStrategy(mSurfaceControl, strategy);
                transaction.apply();
            }
            return rc;
        }

        public void setVisibility(boolean visible) {
            Log.i(TAG,
                    String.format("Setting visibility for %s: %s", mName,
@@ -342,6 +378,11 @@ public class GraphicsActivity extends Activity {
                uniqueFrameRates.add(frameRate);
            }
        }
        Log.i(TAG,
                "**** Available display refresh rates: "
                        + uniqueFrameRates.stream()
                                  .map(Object::toString)
                                  .collect(Collectors.joining(", ")));
        return uniqueFrameRates;
    }

@@ -358,6 +399,10 @@ public class GraphicsActivity extends Activity {
                <= FRAME_RATE_TOLERANCE;
    }

    private boolean frameRateEquals(float frameRate1, float frameRate2) {
        return Math.abs(frameRate1 - frameRate2) <= FRAME_RATE_TOLERANCE;
    }

    // Waits until our SurfaceHolder has a surface and the activity is resumed.
    private void waitForPreconditions() throws InterruptedException {
        assertNotSame(
@@ -447,9 +492,22 @@ public class GraphicsActivity extends Activity {
        verifyCompatibleAndStableFrameRate(0, surfaces);
    }

    // Set expectedFrameRate to 0.0 to verify only stable frame rate.
    private void verifyExactAndStableFrameRate(
            float expectedFrameRate,
            TestSurface... surfaces) throws InterruptedException {
        verifyFrameRate(expectedFrameRate, false, surfaces);
    }

    private void verifyCompatibleAndStableFrameRate(
            float expectedFrameRate, TestSurface... surfaces) throws InterruptedException {
            float expectedFrameRate,
            TestSurface... surfaces) throws InterruptedException {
        verifyFrameRate(expectedFrameRate, true, surfaces);
    }

    // Set expectedFrameRate to 0.0 to verify only stable frame rate.
    private void verifyFrameRate(
            float expectedFrameRate, boolean multiplesAllowed,
            TestSurface... surfaces) throws InterruptedException {
        Log.i(TAG, "Verifying compatible and stable frame rate");
        long nowNanos = System.nanoTime();
        long gracePeriodEndTimeNanos =
@@ -457,10 +515,20 @@ public class GraphicsActivity extends Activity {
        while (true) {
            if (expectedFrameRate > FRAME_RATE_TOLERANCE) { // expectedFrameRate > 0
                // Wait until we switch to a compatible frame rate.
                Log.i(TAG,
                        "Verifying expected frame rate: actual (device)=" + mDeviceFrameRate
                                + " expected=" + expectedFrameRate);
                if (multiplesAllowed) {
                    while (!isFrameRateMultiple(mDeviceFrameRate, expectedFrameRate)
                            && !waitForEvents(gracePeriodEndTimeNanos, surfaces)) {
                        // Empty
                    }
                } else {
                    while (!frameRateEquals(mDeviceFrameRate, expectedFrameRate)
                            && !waitForEvents(gracePeriodEndTimeNanos, surfaces)) {
                        // Empty
                    }
                }
                nowNanos = System.nanoTime();
                if (nowNanos >= gracePeriodEndTimeNanos) {
                    throw new FrameRateTimeoutException(expectedFrameRate, mDeviceFrameRate);
@@ -604,12 +672,65 @@ public class GraphicsActivity extends Activity {
                "frame rate category=" + category);
    }

    private void testSurfaceControlFrameRateSelectionStrategyInternal(int parentStrategy)
            throws InterruptedException {
        Log.i(TAG,
                "**** Running testSurfaceControlFrameRateSelectionStrategy for strategy "
                        + parentStrategy);
        TestSurface parent = null;
        TestSurface child = null;
        try {
            parent = new TestSurface(mSurfaceView.getSurfaceControl(), mSurface,
                    "testSurfaceParent", mSurfaceView.getHolder().getSurfaceFrame(),
                    /*visible=*/true, Color.RED);
            child = new TestSurface(parent.getSurfaceControl(), parent.getSurface(),
                    "testSurfaceChild", mSurfaceView.getHolder().getSurfaceFrame(),
                    /*visible=*/true, Color.BLUE);

            // Test
            Display display = getDisplay();
            List<Float> frameRates = getRefreshRates(display.getMode(), display);
            assumeTrue("**** SKIPPED due to frame rate override disabled",
                    SurfaceFlingerProperties.enable_frame_rate_override().orElse(true));
            float childFrameRate = Collections.max(frameRates);
            float parentFrameRate = childFrameRate / 2;
            int initialNumEvents = mModeChangedEvents.size();
            parent.setFrameRate(parentFrameRate);
            parent.setFrameRateSelectionStrategy(parentStrategy);
            child.setFrameRate(childFrameRate);

            // Verify
            float expectedFrameRate =
                    parentStrategy == SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN
                    ? parentFrameRate
                    : childFrameRate;
            verifyExactAndStableFrameRate(expectedFrameRate, parent, child);
            verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
        } finally {
            if (parent != null) {
                parent.release();
            }
            if (child != null) {
                child.release();
            }
        }
    }

    public void testSurfaceControlFrameRateSelectionStrategy(int parentStrategy)
            throws InterruptedException {
        runTestsWithPreconditions(
                () -> testSurfaceControlFrameRateSelectionStrategyInternal(parentStrategy),
                "frame rate strategy=" + parentStrategy);
    }

    private float getExpectedFrameRate(int category) {
        Display display = getDisplay();
        List<Float> frameRates = getRefreshRates(display.getMode(), display);

        if (category == Surface.FRAME_RATE_CATEGORY_DEFAULT
                || category == Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE) {
        if (category == Surface.FRAME_RATE_CATEGORY_DEFAULT) {
            // Max due to default vote and no other frame rate specifications.
            return Collections.max(frameRates);
        } else if (category == Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE) {
            return Collections.min(frameRates);
        }

+18 −17
Original line number Diff line number Diff line
@@ -23,11 +23,14 @@ import android.hardware.display.DisplayManager;
import android.os.SystemProperties;
import android.support.test.uiautomator.UiDevice;
import android.view.Surface;
import android.view.SurfaceControl;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;

import com.android.compatibility.common.util.DisplayUtil;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -59,19 +62,15 @@ public class SurfaceControlTest {
        uiDevice.executeShellCommand("wm dismiss-keyguard");

        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                Manifest.permission.LOG_COMPAT_CHANGE,
                Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
                Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE,
                Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
                Manifest.permission.MANAGE_GAME_MODE);
                Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS);

        // Prevent DisplayManager from limiting the allowed refresh rate range based on
        // non-app policies (e.g. low battery, user settings, etc).
        mDisplayManager = activity.getSystemService(DisplayManager.class);
        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);

        mInitialRefreshRateSwitchingType =
                toSwitchingType(mDisplayManager.getMatchContentFrameRateUserPreference());
        mInitialRefreshRateSwitchingType = DisplayUtil.getRefreshRateSwitchingType(mDisplayManager);
        mDisplayManager.setRefreshRateSwitchingType(
                DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
    }
@@ -118,16 +117,18 @@ public class SurfaceControlTest {
        activity.testSurfaceControlFrameRateCategory(Surface.FRAME_RATE_CATEGORY_DEFAULT);
    }

    private int toSwitchingType(int matchContentFrameRateUserPreference) {
        switch (matchContentFrameRateUserPreference) {
            case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER:
                return DisplayManager.SWITCHING_TYPE_NONE;
            case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY:
                return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS;
            case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS:
                return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;
            default:
                return -1;
    @Test
    public void testSurfaceControlFrameRateSelectionStrategySelf() throws InterruptedException {
        GraphicsActivity activity = mActivityRule.getActivity();
        activity.testSurfaceControlFrameRateSelectionStrategy(
                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF);
    }

    @Test
    public void testSurfaceControlFrameRateSelectionStrategyOverrideChildren()
            throws InterruptedException {
        GraphicsActivity activity = mActivityRule.getActivity();
        activity.testSurfaceControlFrameRateSelectionStrategy(
                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
    }
}