From 26387cf5959717d6fb439035268e521a237964ee Mon Sep 17 00:00:00 2001 From: Mariia Sandrikova Date: Sat, 24 Dec 2022 06:43:09 +0000 Subject: [PATCH] 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 --- .../java/android/content/pm/ActivityInfo.java | 11 +++ .../com/android/server/wm/ActivityRecord.java | 4 +- .../server/wm/LetterboxConfiguration.java | 58 +++++++++++++- .../server/wm/LetterboxConfigurationTest.java | 43 ++++++++++- .../android/server/wm/SizeCompatTests.java | 76 +++++++++++++++++++ 5 files changed, 184 insertions(+), 8 deletions(-) diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 68a84e8304920..c64b7713464aa 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1152,6 +1152,17 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @TestApi 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. diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c1a68de430a39..fcb135e3c0d4b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -10129,8 +10129,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 { diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 03c5589244309..9b84233272157 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -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. */ diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java index e19670458bb7a..ead1a86757758 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java @@ -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 move) { // We are in the current position diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 850bed6ec88b6..2cce62e1685c5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -56,6 +56,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(