Merge "Add per-app controls for compat fake focus" into tm-qpr-dev
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(