Allow override the suppress Overlay condition

Create interface for allowing the #shouldSuppressOverlay() method could
be overrided by different implementation.

Bug: 358473717
Flag: com.android.systemui.override_suppress_overlay_condition
Test: atest ClipboardListenerTest
Change-Id: Iaa3b518820a923537968c4c67cf047bcb77b9b50
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a9d4c89..d98b05d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1424,6 +1424,13 @@
 }
 
 flag {
+   name: "override_suppress_overlay_condition"
+   namespace: "systemui"
+   description: "Allow override the conditions to suppress the clipboard overlay"
+   bug: "358473717"
+}
+
+flag {
    name: "media_projection_dialog_behind_lockscreen"
    namespace: "systemui"
    description: "Ensure MediaProjection Dialog appears behind the lockscreen"
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 1ada56d..7033e64 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -19,6 +19,7 @@
 import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
 
 import static com.android.systemui.Flags.clipboardNoninteractiveOnLockscreen;
+import static com.android.systemui.Flags.overrideSuppressOverlayCondition;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
@@ -63,6 +64,7 @@
     private final ClipboardManager mClipboardManager;
     private final KeyguardManager mKeyguardManager;
     private final UiEventLogger mUiEventLogger;
+    private final ClipboardOverlaySuppressionController mClipboardOverlaySuppressionController;
     private ClipboardOverlay mClipboardOverlay;
 
     @Inject
@@ -71,13 +73,15 @@
             ClipboardToast clipboardToast,
             UserScopedService<ClipboardManager> clipboardManager,
             KeyguardManager keyguardManager,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            ClipboardOverlaySuppressionController clipboardOverlaySuppressionController) {
         mContext = context;
         mOverlayProvider = clipboardOverlayControllerProvider;
         mClipboardToast = clipboardToast;
         mClipboardManager = clipboardManager.forUser(UserHandle.CURRENT);
         mKeyguardManager = keyguardManager;
         mUiEventLogger = uiEventLogger;
+        mClipboardOverlaySuppressionController = clipboardOverlaySuppressionController;
     }
 
     @Override
@@ -94,9 +98,17 @@
         String clipSource = mClipboardManager.getPrimaryClipSource();
         ClipData clipData = mClipboardManager.getPrimaryClip();
 
-        if (shouldSuppressOverlay(clipData, clipSource, Build.IS_EMULATOR)) {
-            Log.i(TAG, "Clipboard overlay suppressed.");
-            return;
+        if (overrideSuppressOverlayCondition()) {
+            if (mClipboardOverlaySuppressionController.shouldSuppressOverlay(clipData, clipSource,
+                    Build.IS_EMULATOR)) {
+                Log.i(TAG, "Clipboard overlay suppressed.");
+                return;
+            }
+        } else {
+            if (shouldSuppressOverlay(clipData, clipSource, Build.IS_EMULATOR)) {
+                Log.i(TAG, "Clipboard overlay suppressed.");
+                return;
+            }
         }
 
         // user should not access intents before setup or while device is locked
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionController.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionController.kt
new file mode 100644
index 0000000..be592da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionController.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay
+
+import android.content.ClipData
+
+/** Interface to control the clipboard overlay suppression behavior. */
+interface ClipboardOverlaySuppressionController {
+
+    /** Decides if the clipboard overlay should be suppressed. */
+    fun shouldSuppressOverlay(
+        clipData: ClipData?,
+        clipSource: String?,
+        isEmulator: Boolean,
+    ): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImpl.kt
new file mode 100644
index 0000000..814341d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImpl.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay
+
+import android.content.ClipData
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+open class ClipboardOverlaySuppressionControllerImpl @Inject constructor() :
+    ClipboardOverlaySuppressionController {
+
+    // The overlay is suppressed if EXTRA_SUPPRESS_OVERLAY is true and the device is an emulator or
+    // the source package is SHELL_PACKAGE. This is meant to suppress the overlay when the emulator
+    // or a mirrored device is syncing the clipboard.
+    override fun shouldSuppressOverlay(
+        clipData: ClipData?,
+        clipSource: String?,
+        isEmulator: Boolean,
+    ): Boolean {
+        if (!(isEmulator || SHELL_PACKAGE == clipSource)) {
+            return false
+        }
+        if (clipData == null || clipData.description.extras == null) {
+            return false
+        }
+        return clipData.description.extras.getBoolean(EXTRA_SUPPRESS_OVERLAY, false)
+    }
+
+    companion object {
+        @VisibleForTesting const val SHELL_PACKAGE = "com.android.shell"
+
+        @VisibleForTesting
+        const val EXTRA_SUPPRESS_OVERLAY = "com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt
new file mode 100644
index 0000000..527819c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay.dagger
+
+import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionController
+import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionControllerImpl
+import dagger.Binds
+import dagger.Module
+
+/** Dagger Module for code in the clipboard overlay package. */
+@Module
+interface ClipboardOverlaySuppressionModule {
+
+    /** Provides implementation for [ClipboardOverlaySuppressionController]. */
+    @Binds
+    fun provideClipboardOverlaySuppressionController(
+        clipboardOverlaySuppressionControllerImpl: ClipboardOverlaySuppressionControllerImpl
+    ): ClipboardOverlaySuppressionController
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 5c075c2..a94fbd9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.accessibility.SystemActionsModule;
 import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
 import com.android.systemui.battery.BatterySaverModule;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlaySuppressionModule;
 import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
@@ -116,6 +117,7 @@
         AccessibilityRepositoryModule.class,
         AospPolicyModule.class,
         BatterySaverModule.class,
+        ClipboardOverlaySuppressionModule.class,
         CollapsedStatusBarFragmentStartableModule.class,
         ConnectingDisplayViewModel.StartableModule.class,
         DefaultBlueprintModule.class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index d72b72c..a2c36ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -31,6 +32,7 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.ClipboardManager;
+import android.os.Build;
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.platform.test.annotations.DisableFlags;
@@ -71,6 +73,8 @@
     private ClipboardToast mClipboardToast;
     @Mock
     private UiEventLogger mUiEventLogger;
+    @Mock
+    private ClipboardOverlaySuppressionController mClipboardOverlaySuppressionController;
 
     private ClipData mSampleClipData;
     private String mSampleSource = "Example source";
@@ -113,7 +117,8 @@
                     return null;
                 },
                 mKeyguardManager,
-                mUiEventLogger);
+                mUiEventLogger,
+                mClipboardOverlaySuppressionController);
     }
 
 
@@ -155,6 +160,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION)
     public void test_shouldSuppressOverlay() {
         // Regardless of the package or emulator, nothing should be suppressed without the flag
         assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData, mSampleSource,
@@ -236,6 +242,64 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION)
+    public void test_onPrimaryClipChanged_notSuppressOverlay() {
+        when(mClipboardOverlaySuppressionController.shouldSuppressOverlay(any(), any(),
+                anyBoolean())).thenReturn(false);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mClipboardOverlaySuppressionController).shouldSuppressOverlay(mSampleClipData,
+                mSampleSource, Build.IS_EMULATOR);
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+        verify(mOverlayController).setClipData(mSampleClipData, mSampleSource);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION)
+    public void test_onPrimaryClipChanged_suppressOverlay() {
+        when(mClipboardOverlaySuppressionController.shouldSuppressOverlay(any(), any(),
+                anyBoolean())).thenReturn(true);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mClipboardOverlaySuppressionController).shouldSuppressOverlay(mSampleClipData,
+                mSampleSource, Build.IS_EMULATOR);
+        verifyZeroInteractions(mOverlayControllerProvider);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION)
+    public void test_onPrimaryClipChanged_legacyBehavior_notSuppressOverlay() {
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+        verify(mOverlayController).setClipData(mSampleClipData, mSampleSource);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION)
+    public void test_onPrimaryClipChanged_legacyBehavior_suppressOverlay() {
+        ClipDescription desc = new ClipDescription("Test", new String[]{"text/plain"});
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(ClipboardListener.EXTRA_SUPPRESS_OVERLAY, true);
+        desc.setExtras(bundle);
+        ClipData suppressableClipData = new ClipData(desc, new ClipData.Item("Test Item"));
+        when(mClipboardManager.getPrimaryClip()).thenReturn(suppressableClipData);
+        when(mClipboardManager.getPrimaryClipSource()).thenReturn(ClipboardListener.SHELL_PACKAGE);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verifyZeroInteractions(mOverlayControllerProvider);
+    }
+
+    @Test
     public void test_nullClipData_showsNothing() {
         when(mClipboardManager.getPrimaryClip()).thenReturn(null);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt
new file mode 100644
index 0000000..86788d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay
+
+import android.content.ClipData
+import android.content.ClipDescription
+import android.os.PersistableBundle
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ClipboardOverlaySuppressionControllerImplTest : SysuiTestCase() {
+    private lateinit var mSampleClipData: ClipData
+    private lateinit var mSuppressableClipData: ClipData
+    private lateinit var mClipboardOverlaySuppressionControllerImpl:
+        ClipboardOverlaySuppressionControllerImpl
+
+    @Before
+    fun setup() {
+        mSampleClipData = ClipData("Test", arrayOf("text/plain"), ClipData.Item("Test Item"))
+
+        val desc = ClipDescription("Test", arrayOf("text/plain"))
+        val bundle = PersistableBundle()
+        bundle.putBoolean(ClipboardOverlaySuppressionControllerImpl.EXTRA_SUPPRESS_OVERLAY, true)
+        desc.extras = bundle
+        mSuppressableClipData = ClipData(desc, ClipData.Item("Test Item"))
+
+        mClipboardOverlaySuppressionControllerImpl = ClipboardOverlaySuppressionControllerImpl()
+    }
+
+    @Test
+    fun shouldSuppressOverlay_notEmulatorOrShellPackage_returnFalse() {
+        Assert.assertFalse(
+            mClipboardOverlaySuppressionControllerImpl.shouldSuppressOverlay(
+                mSuppressableClipData,
+                EXAMPLE_PACKAGE,
+                false,
+            )
+        )
+    }
+
+    @Test
+    fun shouldSuppressOverlay_nullClipData_returnFalse() {
+        Assert.assertFalse(
+            mClipboardOverlaySuppressionControllerImpl.shouldSuppressOverlay(
+                null,
+                ClipboardOverlaySuppressionControllerImpl.SHELL_PACKAGE,
+                true,
+            )
+        )
+    }
+
+    @Test
+    fun shouldSuppressOverlay_noSuppressOverlayExtra_returnFalse() {
+        // Regardless of the package or emulator, nothing should be suppressed without the flag.
+        Assert.assertFalse(
+            mClipboardOverlaySuppressionControllerImpl.shouldSuppressOverlay(
+                mSampleClipData,
+                EXAMPLE_PACKAGE,
+                true,
+            )
+        )
+        Assert.assertFalse(
+            mClipboardOverlaySuppressionControllerImpl.shouldSuppressOverlay(
+                mSampleClipData,
+                ClipboardOverlaySuppressionControllerImpl.SHELL_PACKAGE,
+                false,
+            )
+        )
+    }
+
+    @Test
+    fun shouldSuppressOverlay_hasSuppressOverlayExtra_returnTrue() {
+        // Clip data with the suppression extra is only honored in the emulator or with the shell
+        // package.
+        Assert.assertTrue(
+            mClipboardOverlaySuppressionControllerImpl.shouldSuppressOverlay(
+                mSuppressableClipData,
+                EXAMPLE_PACKAGE,
+                true,
+            )
+        )
+        Assert.assertTrue(
+            mClipboardOverlaySuppressionControllerImpl.shouldSuppressOverlay(
+                mSuppressableClipData,
+                ClipboardOverlaySuppressionControllerImpl.SHELL_PACKAGE,
+                false,
+            )
+        )
+    }
+
+    companion object {
+        const val EXAMPLE_PACKAGE = "com.example"
+    }
+}