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

Commit dc1d12bf authored by Miranda Kephart's avatar Miranda Kephart
Browse files

Fix clipboard intents in multiuser/hsum

Test: manual
Test: atest ActionIntentCreatorTest
Test: atest ClipboardOverlayControllerTest
Bug: 217922018
Flag: com.android.systemui.clipboard_overlay_multiuser

Change-Id: I7b0cab3a78bbbf889d8deb9d3088ae6548f4a371
parent 9a1aff72
Loading
Loading
Loading
Loading
+57 −8
Original line number Diff line number Diff line
@@ -22,23 +22,28 @@ import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.UserInfo
import android.net.Uri
import android.platform.test.annotations.EnableFlags
import android.text.SpannableString
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.settings.FakeUserTracker
import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@SmallTest
@@ -48,9 +53,21 @@ class ActionIntentCreatorTest : SysuiTestCase() {
    private val mainDispatcher = UnconfinedTestDispatcher(scheduler)
    private val testScope = TestScope(mainDispatcher)
    val packageManager = mock<PackageManager>()
    val userTracker = FakeUserTracker()

    val creator =
        ActionIntentCreator(context, packageManager, testScope.backgroundScope, mainDispatcher)
        ActionIntentCreator(
            context,
            packageManager,
            userTracker,
            testScope.backgroundScope,
            mainDispatcher,
        )

    @Before
    fun setup() {
        userTracker.set(listOf(UserInfo(17, "test user", 0)), 0)
    }

    @Test
    fun test_getTextEditorIntent() {
@@ -81,11 +98,22 @@ class ActionIntentCreatorTest : SysuiTestCase() {
        assertEquals(fakeComponent, intent.component)
    }

    @Test
    @EnableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    fun test_remoteCopyIntent_containsUserId() {
        context.getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, "")

        val clipData = ClipData.newPlainText("Test", "Test Item")
        var intent = creator.getRemoteCopyIntent(clipData, context)

        assertEquals(17, intent.contentUserHint)
    }

    @Test
    fun test_getImageEditIntent_noDefault() = runTest {
        context.getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, "")
        val fakeUri = Uri.parse("content://foo")
        var intent = creator.getImageEditIntent(fakeUri, context)
        val intent = creator.getImageEditIntent(fakeUri)

        assertEquals(Intent.ACTION_EDIT, intent.action)
        assertEquals("image/*", intent.type)
@@ -94,6 +122,16 @@ class ActionIntentCreatorTest : SysuiTestCase() {
        assertFlags(intent, EXTERNAL_INTENT_FLAGS)
    }

    @Test
    @EnableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    fun test_getImageEditIntent_containsUserId() = runTest {
        context.getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, "")
        val fakeUri = Uri.parse("content://foo")
        val intent = creator.getImageEditIntent(fakeUri)

        assertEquals(17, intent.contentUserHint)
    }

    @Test
    fun test_getImageEditIntent_defaultProvided() = runTest {
        val fakeUri = Uri.parse("content://foo")
@@ -103,11 +141,12 @@ class ActionIntentCreatorTest : SysuiTestCase() {
        context
            .getOrCreateTestableResources()
            .addOverride(R.string.config_screenshotEditor, fakeComponent.flattenToString())
        val intent = creator.getImageEditIntent(fakeUri, context)
        val intent = creator.getImageEditIntent(fakeUri)
        assertEquals(fakeComponent, intent.component)
    }

    @Test
    @EnableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    fun test_getImageEditIntent_preferredProvidedButDisabled() = runTest {
        val fakeUri = Uri.parse("content://foo")

@@ -130,8 +169,9 @@ class ActionIntentCreatorTest : SysuiTestCase() {
                R.string.config_preferredScreenshotEditor,
                preferredComponent.flattenToString(),
            )
        val intent = creator.getImageEditIntent(fakeUri, context)
        val intent = creator.getImageEditIntent(fakeUri)
        assertEquals(defaultComponent, intent.component)
        assertEquals(17, intent.contentUserHint)
    }

    @Test
@@ -163,7 +203,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
                R.string.config_preferredScreenshotEditor,
                preferredComponent.flattenToString(),
            )
        val intent = creator.getImageEditIntent(fakeUri, context)
        val intent = creator.getImageEditIntent(fakeUri)
        assertEquals(preferredComponent, intent.component)
    }

@@ -179,10 +219,19 @@ class ActionIntentCreatorTest : SysuiTestCase() {
        assertEquals("text/plain", target?.type)
    }

    @Test
    @EnableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    fun test_getShareIntent_containsUserId() {
        val clipData = ClipData.newPlainText("Test", "Test Item")
        val intent = creator.getShareIntent(clipData, context)

        assertEquals(17, intent.contentUserHint)
    }

    @Test
    fun test_getShareIntent_html() {
        val clipData = ClipData.newHtmlText("Test", "Some HTML", "<b>Some HTML</b>")
        val intent = creator.getShareIntent(clipData, getContext())
        val intent = creator.getShareIntent(clipData, context)

        assertEquals(Intent.ACTION_CHOOSER, intent.action)
        assertFlags(intent, EXTERNAL_INTENT_FLAGS)
+50 −7
Original line number Diff line number Diff line
@@ -16,13 +16,17 @@

package com.android.systemui.clipboardoverlay;

import static com.android.systemui.Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import android.content.ClipData;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.text.SpannableString;

import androidx.test.filters.SmallTest;
@@ -30,10 +34,13 @@ import androidx.test.runner.AndroidJUnit4;

import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
import com.android.systemui.settings.FakeUserTracker;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

@SmallTest
@@ -41,8 +48,14 @@ import java.util.concurrent.atomic.AtomicReference;
public class DefaultIntentCreatorTest extends SysuiTestCase {
    private static final int EXTERNAL_INTENT_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK
            | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION;
    private final FakeUserTracker mFakeUserTracker = new FakeUserTracker();

    private final DefaultIntentCreator mIntentCreator  = new DefaultIntentCreator(mFakeUserTracker);

    private final DefaultIntentCreator mIntentCreator  = new DefaultIntentCreator();
    @Before
    public void setup() {
        mFakeUserTracker.set(List.of(new UserInfo(17, "test user", 0)), 0);
    }

    @Test
    public void test_getTextEditorIntent() {
@@ -74,15 +87,25 @@ public class DefaultIntentCreatorTest extends SysuiTestCase {
        assertEquals(fakeComponent, intent.getComponent());
    }

    @Test
    @EnableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    public void test_getRemoteCopyIntent_setsUserId() {
        getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage,
                "");

        ClipData clipData = ClipData.newPlainText("Test", "Test Item");
        Intent intent = mIntentCreator.getRemoteCopyIntent(clipData, getContext());

        assertEquals(17, intent.getContentUserHint());
    }

    @Test
    public void test_getImageEditIntentAsync() {
        getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
                "");
        Uri fakeUri = Uri.parse("content://foo");
        final AtomicReference<Intent> intentHolder = new AtomicReference<>(null);
        mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), output -> {
            intentHolder.set(output);
        });
        mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), intentHolder::set);

        Intent intent = intentHolder.get();
        assertEquals(Intent.ACTION_EDIT, intent.getAction());
@@ -96,12 +119,23 @@ public class DefaultIntentCreatorTest extends SysuiTestCase {
                "com.android.remotecopy.RemoteCopyActivity");
        getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
                fakeComponent.flattenToString());
        mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), output -> {
            intentHolder.set(output);
        });
        mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), intentHolder::set);
        assertEquals(fakeComponent, intentHolder.get().getComponent());
    }

    @Test
    @EnableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    public void test_getImageEditAsync_setsUserId() {
        getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
                "");
        Uri fakeUri = Uri.parse("content://foo");
        final AtomicReference<Intent> intentHolder = new AtomicReference<>(null);
        mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), intentHolder::set);

        Intent intent = intentHolder.get();
        assertEquals(17, intent.getContentUserHint());
    }

    @Test
    public void test_getShareIntent_plaintext() {
        ClipData clipData = ClipData.newPlainText("Test", "Test Item");
@@ -114,6 +148,15 @@ public class DefaultIntentCreatorTest extends SysuiTestCase {
        assertEquals("text/plain", target.getType());
    }

    @Test
    @EnableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    public void test_getShareIntent_setsUserId() {
        ClipData clipData = ClipData.newPlainText("Test", "Test Item");
        Intent intent = mIntentCreator.getShareIntent(clipData, getContext());

        assertEquals(17, intent.getContentUserHint());
    }

    @Test
    public void test_getShareIntent_html() {
        ClipData clipData = ClipData.newHtmlText("Test", "Some HTML",
+19 −5
Original line number Diff line number Diff line
@@ -25,10 +25,12 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri
import android.text.TextUtils
import com.android.systemui.Flags.clipboardOverlayMultiuser
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -42,6 +44,7 @@ class ActionIntentCreator
constructor(
    private val context: Context,
    private val packageManager: PackageManager,
    private val userTracker: UserTracker,
    @Application private val applicationScope: CoroutineScope,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) : IntentCreator {
@@ -76,12 +79,17 @@ constructor(
            }
        }

        return Intent.createChooser(shareIntent, null)
        val chooserIntent =
            Intent.createChooser(shareIntent, null)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        if (clipboardOverlayMultiuser()) {
            chooserIntent.prepareToLeaveUser(userTracker.userId)
        }
        return chooserIntent
    }

    suspend fun getImageEditIntent(uri: Uri?, context: Context): Intent {
    suspend fun getImageEditIntent(uri: Uri?): Intent {
        return Intent(Intent.ACTION_EDIT).apply {
            // Use the preferred editor if it's available, otherwise fall back to the default editor
            component = preferredEditor() ?: defaultEditor()
@@ -89,6 +97,9 @@ constructor(
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
            putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD)
            if (clipboardOverlayMultiuser()) {
                prepareToLeaveUser(userTracker.userId)
            }
        }
    }

@@ -97,7 +108,7 @@ constructor(
        context: Context,
        outputConsumer: Consumer<Intent>,
    ) {
        applicationScope.launch { outputConsumer.accept(getImageEditIntent(uri, context)) }
        applicationScope.launch { outputConsumer.accept(getImageEditIntent(uri)) }
    }

    override fun getRemoteCopyIntent(clipData: ClipData?, context: Context): Intent {
@@ -110,6 +121,9 @@ constructor(
            setClipData(clipData)
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
            if (clipboardOverlayMultiuser()) {
                prepareToLeaveUser(userTracker.userId)
            }
        }
    }

+26 −7
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;

import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.systemui.Flags.clipboardAnnounceLiveRegion;
import static com.android.systemui.Flags.clipboardOverlayMultiuser;
import static com.android.systemui.Flags.showClipboardIndication;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
@@ -58,8 +59,11 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.plugins.ActivityStartOptions;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.TimeoutHandler;
import com.android.systemui.settings.UserTracker;

import kotlin.Unit;

@@ -92,6 +96,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
    private final ClipboardImageLoader mClipboardImageLoader;
    private final ClipboardTransitionExecutor mTransitionExecutor;
    private final ClipboardInputEventReceiver mClipboardInputEventReceiver;
    private final ActivityStarter mActivityStarter;
    private final UserTracker mUserTracker;


    private final ClipboardOverlayView mView;
@@ -125,6 +131,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
            BroadcastDispatcher broadcastDispatcher,
            BroadcastSender broadcastSender,
            TimeoutHandler timeoutHandler,
            ActivityStarter activityStarter,
            UserTracker userTracker,
            ClipboardOverlayUtils clipboardUtils,
            @Background Executor bgExecutor,
            ClipboardImageLoader clipboardImageLoader,
@@ -139,6 +147,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
        mTransitionExecutor = transitionExecutor;
        mClipboardInputEventReceiver = clipboardInputEventReceiver;
        mClipboardIndicationProvider = clipboardIndicationProvider;
        mActivityStarter = activityStarter;
        mUserTracker = userTracker;

        mClipboardLogger = new ClipboardLogger(uiEventLogger);
        mIntentCreator = intentCreator;
@@ -475,8 +485,15 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
                if (!mCancelled) {
                    mClipboardLogger.logSessionComplete(event);
                    if (intent != null) {
                        if (clipboardOverlayMultiuser()) {
                            mActivityStarter.startActivityDismissingKeyguard(
                                    new ActivityStartOptions(intent, false, false, null,
                                            intent.getFlags(), null, null, false,
                                            mUserTracker.getUserHandle(), null));
                        } else {
                            mContext.startActivity(intent);
                        }
                    }
                    hideImmediate();
                }
            }
@@ -529,13 +546,15 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
    @Override
    public void onRemoteCopyButtonTapped() {
        finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
                mIntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
                mIntentCreator.getRemoteCopyIntent(
                        mClipboardModel.getClipData(), mContext));
    }

    @Override
    public void onShareButtonTapped() {
        Intent shareIntent =
                mIntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext);
                mIntentCreator.getShareIntent(
                        mClipboardModel.getClipData(), mContext);
        switch (mClipboardModel.getType()) {
            case TEXT:
            case URI:
@@ -555,10 +574,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
                        mIntentCreator.getTextEditorIntent(mContext));
                break;
            case IMAGE:
                mIntentCreator.getImageEditIntentAsync(mClipboardModel.getUri(), mContext,
                        intent -> {
                            finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED, intent);
                        });
                mIntentCreator.getImageEditIntentAsync(
                        mClipboardModel.getUri(), mContext,
                        intent -> finishWithSharedTransition(
                                CLIPBOARD_OVERLAY_EDIT_TAPPED, intent));
                break;
            default:
                Log.w(TAG, "Got preview tapped callback for non-editable type "
+24 −4
Original line number Diff line number Diff line
@@ -31,12 +31,21 @@ import android.view.View
import android.view.Window
import android.view.WindowManagerGlobal
import com.android.internal.app.ChooserActivity
import com.android.systemui.Flags.clipboardOverlayMultiuser
import com.android.systemui.plugins.ActivityStartOptions
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.settings.UserTracker
import javax.inject.Inject

class ClipboardTransitionExecutor
@Inject
constructor(val context: Context, val displayTracker: DisplayTracker) {
constructor(
    val context: Context,
    val userTracker: UserTracker,
    val displayTracker: DisplayTracker,
    val activityStarter: ActivityStarter,
) {
    fun startSharedTransition(window: Window, view: View, intent: Intent, onReady: Runnable) {
        val transition: Pair<ActivityOptions, ExitTransitionCoordinator> =
            ActivityOptions.startSharedElementAnimation(
@@ -53,10 +62,21 @@ constructor(val context: Context, val displayTracker: DisplayTracker) {
                    override fun onFinish() {}
                },
                null,
                Pair.create(view, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
                Pair.create(view, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME),
            )
        transition.second.startExit()
        if (clipboardOverlayMultiuser()) {
            activityStarter.startActivityDismissingKeyguard(
                ActivityStartOptions(
                    intent,
                    flags = intent.flags,
                    userHandle = userTracker.userHandle,
                    activityOptions = transition.first,
                )
            )
        } else {
            context.startActivity(intent, transition.first.toBundle())
        }
        val runner = RemoteAnimationAdapter(NULL_ACTIVITY_TRANSITION, 0, 0)
        try {
            checkNotNull(WindowManagerGlobal.getWindowManagerService())
@@ -79,7 +99,7 @@ constructor(val context: Context, val displayTracker: DisplayTracker) {
                apps: Array<RemoteAnimationTarget>,
                wallpapers: Array<RemoteAnimationTarget>,
                nonApps: Array<RemoteAnimationTarget>,
                finishedCallback: IRemoteAnimationFinishedCallback
                finishedCallback: IRemoteAnimationFinishedCallback,
            ) {
                try {
                    finishedCallback.onAnimationFinished()
Loading