diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 6a9317f2b5432..f526277a0a37d 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -20,10 +20,14 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBO import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED; +import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.os.SystemProperties; import android.provider.DeviceConfig; +import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; @@ -37,6 +41,13 @@ import javax.inject.Inject; @SysUISingleton public class ClipboardListener extends CoreStartable implements ClipboardManager.OnPrimaryClipChangedListener { + private static final String TAG = "ClipboardListener"; + + @VisibleForTesting + static final String SHELL_PACKAGE = "com.android.shell"; + @VisibleForTesting + static final String EXTRA_SUPPRESS_OVERLAY = + "com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY"; private final DeviceConfigProxy mDeviceConfig; private final ClipboardOverlayControllerFactory mOverlayFactory; @@ -68,18 +79,44 @@ public class ClipboardListener extends CoreStartable if (!mClipboardManager.hasPrimaryClip()) { return; } + String clipSource = mClipboardManager.getPrimaryClipSource(); + ClipData clipData = mClipboardManager.getPrimaryClip(); + + if (shouldSuppressOverlay(clipData, clipSource, isEmulator())) { + Log.i(TAG, "Clipboard overlay suppressed."); + return; + } + if (mClipboardOverlayController == null) { mClipboardOverlayController = mOverlayFactory.create(mContext); mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource); } else { mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource); } - mClipboardOverlayController.setClipData( - mClipboardManager.getPrimaryClip(), clipSource); + mClipboardOverlayController.setClipData(clipData, clipSource); mClipboardOverlayController.setOnSessionCompleteListener(() -> { // Session is complete, free memory until it's needed again. mClipboardOverlayController = null; }); } + + // 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. + @VisibleForTesting + static boolean shouldSuppressOverlay(ClipData clipData, String clipSource, + boolean isEmulator) { + if (!(isEmulator || SHELL_PACKAGE.equals(clipSource))) { + return false; + } + if (clipData == null || clipData.getDescription().getExtras() == null) { + return false; + } + return clipData.getDescription().getExtras().getBoolean(EXTRA_SUPPRESS_OVERLAY, false); + } + + private static boolean isEmulator() { + return SystemProperties.getBoolean("ro.boot.qemu", false); + } } 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 de04d3e9d0594..91214a85ddd59 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java @@ -19,6 +19,8 @@ package com.android.systemui.clipboardoverlay; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -26,7 +28,9 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.ClipData; +import android.content.ClipDescription; import android.content.ClipboardManager; +import android.os.PersistableBundle; import android.provider.DeviceConfig; import androidx.test.filters.SmallTest; @@ -139,4 +143,30 @@ public class ClipboardListenerTest extends SysuiTestCase { verify(mClipboardOverlayControllerFactory, times(2)).create(any()); } + + @Test + public void test_shouldSuppressOverlay() { + // Regardless of the package or emulator, nothing should be suppressed without the flag + assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData, mSampleSource, + false)); + assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData, + ClipboardListener.SHELL_PACKAGE, false)); + assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData, mSampleSource, + true)); + + 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")); + + // Clip data with the suppression extra is only honored in the emulator or with the shell + // package. + assertFalse(ClipboardListener.shouldSuppressOverlay(suppressableClipData, mSampleSource, + false)); + assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData, mSampleSource, + true)); + assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData, + ClipboardListener.SHELL_PACKAGE, false)); + } }