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

Commit c243a01d authored by Mariia Sandrikova's avatar Mariia Sandrikova
Browse files

Add per-app controls for compat fake focus

Per-package controls introduced in this change:
- OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS for device manufactures to opt in a package to receive compat fake focus.
- com.android.COMPAT_FAKE_FOCUS_OPT_IN for app developers to opt in via component property to receive compat fake focus.
- com.android.COMPAT_FAKE_FOCUS_OPT_OUT for app developers to opt out via component property from receiving compat fake focus.

This change is needed because some game engines wait to get focus before drawing the content of the app so fake focus helps them to avoid staying blacked out when they are resumed and do not have focus yet.

Fix: 261828290
Fix: 263259275
Test: atest WmTests:LetterboxConfigurationTest#testIsFakeFocusEnabledOnDevice
Test: atest WmTests:SizeCompatTests#testShouldSendFakeFocus_overrideEnabled_returnsTrue
Test: atest WmTests:SizeCompatTests#testShouldSendFakeFocus_overrideDisabled_returnsFalse
Test: atest WmTests:SizeCompatTests#testIsFakeFocusEnabled_optOutPropertyEnabled_overrideEnabled_fakeFocusDisabled
Test: atest WmTests:SizeCompatTests#testIsFakeFocusEnabled_optInPropertyEnabled_overrideDisabled_fakeFocusEnabled
Test: atest WmTests:SizeCompatTests#testIsFakeFocusEnabled_optOutPropertyEnabled_fakeFocusDisabled
Test: atest WmTests:SizeCompatTests#testIsFakeFocusEnabled_optInPropertyEnabled_fakeFocusEnabled
Change-Id: Ib16084728bc0d5870c8d2effc5dd0d54bfe04d24
Merged-In: Ib16084728bc0d5870c8d2effc5dd0d54bfe04d24
parent eeaee6e7
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -1128,6 +1128,17 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
    @Overridable
    public static final long OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN = 218959984L;

    /**
     * Enables sending fake focus for unfocused apps in splitscreen. Some game engines
     * wait to get focus before drawing the content of the app so fake focus helps them to avoid
     * staying blacked out when they are resumed and do not have focus yet.
     * @hide
     */
    @ChangeId
    @Disabled
    @Overridable
    public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L;

    /**
     * Compares activity window layout min width/height with require space for multi window to
     * determine if it can be put into multi window mode.
+2 −2
Original line number Diff line number Diff line
@@ -10127,8 +10127,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
    // TODO(b/263592337): Explore enabling compat fake focus for fullscreen, e.g. for when
    // covered with bubbles.
    boolean shouldSendCompatFakeFocus() {
        return mWmService.mLetterboxConfiguration.isCompatFakeFocusEnabled() && inMultiWindowMode()
                && !inPinnedWindowingMode() && !inFreeformWindowingMode();
        return mWmService.mLetterboxConfiguration.isCompatFakeFocusEnabled(info)
                && inMultiWindowMode() && !inPinnedWindowingMode() && !inFreeformWindowingMode();
    }

    static class Builder {
+54 −4
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.provider.DeviceConfig;
import android.util.Slog;
@@ -39,6 +41,10 @@ final class LetterboxConfiguration {

    private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfiguration" : TAG_ATM;

    @VisibleForTesting
    static final String DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS =
            "enable_compat_fake_focus";

    /**
     * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
     * set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -108,6 +114,12 @@ final class LetterboxConfiguration {
    /** Letterboxed app window is aligned to the right side. */
    static final int LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM = 2;

    @VisibleForTesting
    static final String PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN = "com.android.COMPAT_FAKE_FOCUS_OPT_IN";
    @VisibleForTesting
    static final String PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT =
            "com.android.COMPAT_FAKE_FOCUS_OPT_OUT";

    final Context mContext;

    // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
@@ -977,11 +989,49 @@ final class LetterboxConfiguration {
                "enable_translucent_activity_letterbox", false);
    }

    // TODO(b/262866240): Add listener to check for device config property
    @VisibleForTesting
    boolean getPackageManagerProperty(PackageManager pm, String property) {
        boolean enabled = false;
        try {
            final PackageManager.Property p = pm.getProperty(property, mContext.getPackageName());
            enabled = p.getBoolean();
        } catch (PackageManager.NameNotFoundException e) {
            // Property not found
        }
        return enabled;
    }

    @VisibleForTesting
    boolean isCompatFakeFocusEnabled(ActivityInfo info) {
        if (!isCompatFakeFocusEnabledOnDevice()) {
            return false;
        }
        // See if the developer has chosen to opt in / out of treatment
        PackageManager pm = mContext.getPackageManager();
        if (getPackageManagerProperty(pm, PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT)) {
            return false;
        } else if (getPackageManagerProperty(pm, PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN)) {
            return true;
        }
        if (info.isChangeEnabled(ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS)) {
            return true;
        }
        return false;
    }

    /** Whether fake sending focus is enabled for unfocused apps in splitscreen */
    boolean isCompatFakeFocusEnabled() {
        return mIsCompatFakeFocusEnabled && DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_WINDOW_MANAGER, "enable_compat_fake_focus", true);
    boolean isCompatFakeFocusEnabledOnDevice() {
        return mIsCompatFakeFocusEnabled
                && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                        DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, true);
    }

    /**
     * Overrides whether fake sending focus is enabled for unfocused apps in splitscreen
     */
    @VisibleForTesting
    void setIsCompatFakeFocusEnabled(boolean enabled) {
        mIsCompatFakeFocusEnabled = enabled;
    }

    /** Whether camera compatibility treatment is enabled. */
+41 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.wm;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.android.server.wm.LetterboxConfiguration.DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
@@ -25,6 +26,8 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_RE
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -34,6 +37,7 @@ import static org.mockito.Mockito.when;

import android.content.Context;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;

import androidx.test.filters.SmallTest;

@@ -43,18 +47,25 @@ import org.junit.Test;
import java.util.Arrays;
import java.util.function.BiConsumer;

/**
 * Tests for the {@link LetterboxConfiguration} class.
 *
 * Build/Install/Run:
 *  atest WmTests:LetterboxConfigurationTests
 */
@SmallTest
@Presubmit
public class LetterboxConfigurationTest {

    private Context mContext;
    private LetterboxConfiguration mLetterboxConfiguration;
    private LetterboxConfigurationPersister mLetterboxConfigurationPersister;

    @Before
    public void setUp() throws Exception {
        Context context = getInstrumentation().getTargetContext();
        mContext = getInstrumentation().getTargetContext();
        mLetterboxConfigurationPersister = mock(LetterboxConfigurationPersister.class);
        mLetterboxConfiguration = new LetterboxConfiguration(context,
        mLetterboxConfiguration = new LetterboxConfiguration(mContext,
                mLetterboxConfigurationPersister);
    }

@@ -222,6 +233,34 @@ public class LetterboxConfigurationTest {
                LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
    }

    @Test
    public void testIsCompatFakeFocusEnabledOnDevice() {
        boolean wasFakeFocusEnabled = DeviceConfig
                .getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, false);

        // Set runtime flag to true and build time flag to false
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
        mLetterboxConfiguration.setIsCompatFakeFocusEnabled(false);
        assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());

        // Set runtime flag to false and build time flag to true
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "false", false);
        mLetterboxConfiguration.setIsCompatFakeFocusEnabled(true);
        assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());

        // Set runtime flag to true so that both are enabled
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
        assertTrue(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());

        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, Boolean.toString(wasFakeFocusEnabled),
                false);
    }

    private void assertForHorizontalMove(int from, int expected, int expectedTime,
            boolean halfFoldPose, BiConsumer<LetterboxConfiguration, Boolean> move) {
        // We are in the current position
+76 −0
Original line number Diff line number Diff line
@@ -53,6 +53,8 @@ import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.LetterboxConfiguration.PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN;
import static com.android.server.wm.LetterboxConfiguration.PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT;
import static com.android.server.wm.WindowContainer.POSITION_TOP;

import static com.google.common.truth.Truth.assertThat;
@@ -3228,6 +3230,80 @@ public class SizeCompatTests extends WindowTestsBase {
        assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
    }

    private ActivityRecord setUpActivityForCompatFakeFocusTest() {
        final ActivityRecord activity = new ActivityBuilder(mAtm)
                .setCreateTask(true)
                .setOnTop(true)
                // Set the component to be that of the test class in order to enable compat changes
                .setComponent(ComponentName.createRelative(mContext,
                        com.android.server.wm.SizeCompatTests.class.getName()))
                .build();
        final Task task = activity.getTask();
        task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
        spyOn(activity.mWmService.mLetterboxConfiguration);
        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
                .isCompatFakeFocusEnabledOnDevice();
        return activity;
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
    public void testShouldSendFakeFocus_overrideEnabled_returnsTrue() {
        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();

        assertTrue(activity.shouldSendCompatFakeFocus());
    }

    @Test
    @DisableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
    public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() {
        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();

        assertFalse(activity.shouldSendCompatFakeFocus());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
    public void testIsCompatFakeFocusEnabled_optOutPropertyAndOverrideEnabled_fakeFocusDisabled() {
        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
                .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT));

        assertFalse(activity.mWmService.mLetterboxConfiguration
                .isCompatFakeFocusEnabled(activity.info));
    }

    @Test
    @DisableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
    public void testIsCompatFakeFocusEnabled_optInPropertyEnabled_noOverride_fakeFocusEnabled() {
        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
                .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN));

        assertTrue(activity.mWmService.mLetterboxConfiguration
                .isCompatFakeFocusEnabled(activity.info));
    }

    @Test
    public void testIsCompatFakeFocusEnabled_optOutPropertyEnabled_fakeFocusDisabled() {
        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
                .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT));

        assertFalse(activity.mWmService.mLetterboxConfiguration
                .isCompatFakeFocusEnabled(activity.info));
    }

    @Test
    public void testIsCompatFakeFocusEnabled_optInPropertyEnabled_fakeFocusEnabled() {
        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
                .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN));

        assertTrue(activity.mWmService.mLetterboxConfiguration
                .isCompatFakeFocusEnabled(activity.info));
    }

    private int getExpectedSplitSize(int dimensionToSplit) {
        int dividerWindowWidth =
                mActivity.mWmService.mContext.getResources().getDimensionPixelSize(