Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6da7d865 authored by Miranda Kephart's avatar Miranda Kephart
Browse files

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
parent 25598f8f
Loading
Loading
Loading
Loading
+29 −5
Original line number Diff line number Diff line
@@ -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();
+46 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -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)
+39 −1
Original line number Diff line number Diff line
@@ -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();
    }
}
+103 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}