diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 9f338d1c6e1d8..c853671519c00 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -31,6 +31,7 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT; +import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR; import static java.util.Objects.requireNonNull; @@ -73,6 +74,7 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.screenshot.TimeoutHandler; import java.io.IOException; @@ -101,6 +103,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private final ClipboardOverlayWindow mWindow; private final TimeoutHandler mTimeoutHandler; private final TextClassifier mTextClassifier; + private final ClipboardOverlayUtils mClipboardUtils; + private final FeatureFlags mFeatureFlags; private final ClipboardOverlayView mView; @@ -119,11 +123,15 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private Animator mExitAnimator; private Animator mEnterAnimator; + private Runnable mOnUiUpdate; + private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks = new ClipboardOverlayView.ClipboardOverlayCallbacks() { @Override public void onInteraction() { - mTimeoutHandler.resetTimeout(); + if (mOnUiUpdate != null) { + mOnUiUpdate.run(); + } } @Override @@ -178,7 +186,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv ClipboardOverlayWindow clipboardOverlayWindow, BroadcastDispatcher broadcastDispatcher, BroadcastSender broadcastSender, - TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) { + TimeoutHandler timeoutHandler, + FeatureFlags featureFlags, + ClipboardOverlayUtils clipboardUtils, + UiEventLogger uiEventLogger) { mBroadcastDispatcher = broadcastDispatcher; mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); final Context displayContext = context.createDisplayContext(getDefaultDisplay()); @@ -199,6 +210,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv mTimeoutHandler = timeoutHandler; mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS); + mFeatureFlags = featureFlags; + mClipboardUtils = clipboardUtils; + mView.setCallbacks(mClipboardCallbacks); @@ -257,11 +271,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null && clipData.getDescription().getExtras() .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE); + boolean isRemote = mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) + && mClipboardUtils.isRemoteCopy(mContext, clipData, clipSource); if (clipData == null || clipData.getItemCount() == 0) { mView.showDefaultTextPreview(); } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) { ClipData.Item item = clipData.getItemAt(0); - if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, + if (isRemote || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) { if (item.getTextLinks() != null) { AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource)); @@ -287,7 +303,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv maybeShowRemoteCopy(clipData); animateIn(); mView.announceForAccessibility(accessibilityAnnouncement); - mTimeoutHandler.resetTimeout(); + if (isRemote) { + mTimeoutHandler.cancelTimeout(); + mOnUiUpdate = null; + } else { + mOnUiUpdate = mTimeoutHandler::resetTimeout; + mOnUiUpdate.run(); + } } private void maybeShowRemoteCopy(ClipData clipData) { @@ -427,7 +449,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - mTimeoutHandler.resetTimeout(); + if (mOnUiUpdate != null) { + mOnUiUpdate.run(); + } } }); mEnterAnimator.start(); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java new file mode 100644 index 0000000000000..cece764580d15 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java @@ -0,0 +1,46 @@ +/* + * 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; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.ComponentName; +import android.content.Context; + +import com.android.systemui.R; + +import javax.inject.Inject; + +class ClipboardOverlayUtils { + + @Inject + ClipboardOverlayUtils() { + } + + boolean isRemoteCopy(Context context, ClipData clipData, String clipSource) { + if (clipData != null && clipData.getDescription().getExtras() != null + && clipData.getDescription().getExtras().getBoolean( + ClipDescription.EXTRA_IS_REMOTE_DEVICE)) { + ComponentName remoteComponent = ComponentName.unflattenFromString( + context.getResources().getString(R.string.config_remoteCopyPackage)); + if (remoteComponent != null) { + return remoteComponent.getPackageName().equals(clipSource); + } + } + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 0d02e9d9465b3..7094e9222e477 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -316,6 +316,7 @@ object Flags { // 1700 - clipboard @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700) + @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701) // 1800 - shade container @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, teamfood = true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index b7f1c1a9f001e..a872e4b96cce8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -19,7 +19,9 @@ package com.android.systemui.clipboardoverlay; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED; +import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -37,6 +39,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.screenshot.TimeoutHandler; import org.junit.After; @@ -62,7 +65,10 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Mock private TimeoutHandler mTimeoutHandler; @Mock + private ClipboardOverlayUtils mClipboardUtils; + @Mock private UiEventLogger mUiEventLogger; + private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private Animator mAnimator; @@ -73,7 +79,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { private ArgumentCaptor mOverlayCallbacksCaptor; private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks; - @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -84,6 +89,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mSampleClipData = new ClipData("Test", new String[]{"text/plain"}, new ClipData.Item("Test Item")); + mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false); + mOverlayController = new ClipboardOverlayController( mContext, mClipboardOverlayView, @@ -91,6 +98,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { getFakeBroadcastDispatcher(), mBroadcastSender, mTimeoutHandler, + mFeatureFlags, + mClipboardUtils, mUiEventLogger); verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); mCallbacks = mOverlayCallbacksCaptor.getValue(); @@ -186,4 +195,33 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED); verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED); } + + @Test + public void test_remoteCopy_withFlagOn() { + mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true); + when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true); + + mOverlayController.setClipData(mSampleClipData, ""); + + verify(mTimeoutHandler, never()).resetTimeout(); + } + + @Test + public void test_remoteCopy_withFlagOff() { + when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true); + + mOverlayController.setClipData(mSampleClipData, ""); + + verify(mTimeoutHandler).resetTimeout(); + } + + @Test + public void test_nonRemoteCopy() { + mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true); + when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false); + + mOverlayController.setClipData(mSampleClipData, ""); + + verify(mTimeoutHandler).resetTimeout(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java new file mode 100644 index 0000000000000..09b1699d3ffcf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java @@ -0,0 +1,103 @@ +/* + * 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; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.os.PersistableBundle; +import android.testing.TestableResources; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ClipboardOverlayUtilsTest extends SysuiTestCase { + + private ClipboardOverlayUtils mClipboardUtils; + + @Before + public void setUp() { + mClipboardUtils = new ClipboardOverlayUtils(); + } + + @Test + public void test_extra_withPackage_returnsTrue() { + PersistableBundle b = new PersistableBundle(); + b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, true); + ClipData data = constructClipData( + new String[]{"text/plain"}, new ClipData.Item("6175550000"), b); + TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride( + R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity"); + + assertTrue(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote")); + } + + @Test + public void test_noExtra_returnsFalse() { + ClipData data = constructClipData( + new String[]{"text/plain"}, new ClipData.Item("6175550000"), null); + TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride( + R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity"); + + assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote")); + } + + @Test + public void test_falseExtra_returnsFalse() { + PersistableBundle b = new PersistableBundle(); + b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, false); + ClipData data = constructClipData( + new String[]{"text/plain"}, new ClipData.Item("6175550000"), b); + TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride( + R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity"); + + assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote")); + } + + @Test + public void test_wrongPackage_returnsFalse() { + PersistableBundle b = new PersistableBundle(); + b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, true); + ClipData data = constructClipData( + new String[]{"text/plain"}, new ClipData.Item("6175550000"), b); + + assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "")); + } + + static ClipData constructClipData(String[] mimeTypes, ClipData.Item item, + PersistableBundle extras) { + ClipDescription description = new ClipDescription("Test", mimeTypes); + if (extras != null) { + description.setExtras(extras); + } + return new ClipData(description, item); + } +}