Persist copy UI from remote devices

Flag: CLIPBOARD_REMOTE_BEHAVIOR (1701)
Bug: 246808749
Test: manual (sent copies back and forth with the extra forced
to true, verified that behavior changed iff the flag was on)
atest ClipboardOverlayControllerTest, atest ClipboardOverlayUtilsTest

Change-Id: Ib68ec1ee9d7af5cc0ea97b20b8db9bad28dc8998
This commit is contained in:
Miranda Kephart
2022-10-18 09:58:49 -04:00
parent 25598f8fae
commit 6da7d86503
5 changed files with 218 additions and 6 deletions

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -315,6 +315,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)

View File

@@ -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<ClipboardOverlayView.ClipboardOverlayCallbacks> 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();
}
}

View File

@@ -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);
}
}