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
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 4be5e14..06399b9 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1129,6 +1129,17 @@
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 f38633f..c712167 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -10127,8 +10127,8 @@
// 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 03c5589..9b84233 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.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 @@
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 @@
/** 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 @@
"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 e196704..ead1a86 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 @@
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_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 android.content.Context;
import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
import androidx.test.filters.SmallTest;
@@ -43,18 +47,25 @@
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 @@
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
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 7488e1c..e9080ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -53,6 +53,8 @@
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 @@
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(