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

Commit 6860d185 authored by Miranda Kephart's avatar Miranda Kephart
Browse files

Use UserTracker to get the correct clipboard manager

Currently we only start the clipboard manager once, so it doesn't work
after switching to a secondary user. This change listens for user
switches and updates the clipboard and keyguard managers to be
associated with the current user.

Note that there is still an issue causing the clipboard overlay window
to not display for secondary users. This is a preliminary change that
just fixes the issue of listening to the correct user.

Bug: 217922018
Test: manual via adding a secondary user, verified via logs
Flag: com.android.systemui.clipboard_overlay_multiuser
Change-Id: Ia9aa6f1a79d8f964c0b3831f901b6c66edeea3d0
parent d322e4e7
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -678,6 +678,16 @@ flag {
    bug: "368308908"
}

flag {
    name: "clipboard_overlay_multiuser"
    namespace: "systemui"
    description: "Fix clipboard overlay for secondary users"
    bug: "217922018"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "clipboard_shared_transitions"
    namespace: "systemui"
+46 −10
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.clipboardoverlay;
import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;

import static com.android.systemui.Flags.clipboardNoninteractiveOnLockscreen;
import static com.android.systemui.Flags.clipboardOverlayMultiuser;
import static com.android.systemui.Flags.overrideSuppressOverlayCondition;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
@@ -35,12 +36,18 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;

import androidx.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.user.utils.UserScopedService;

import java.util.concurrent.Executor;

import javax.inject.Inject;
import javax.inject.Provider;

@@ -61,42 +68,71 @@ public class ClipboardListener implements
    private final Context mContext;
    private final Provider<ClipboardOverlayController> mOverlayProvider;
    private final ClipboardToast mClipboardToast;
    private final ClipboardManager mClipboardManager;
    private final KeyguardManager mKeyguardManager;
    private final UserScopedService<ClipboardManager> mClipboardManagerProvider;
    private final UserScopedService<KeyguardManager> mKeyguardManagerProvider;
    private final UiEventLogger mUiEventLogger;
    private final ClipboardOverlaySuppressionController mClipboardOverlaySuppressionController;
    private ClipboardOverlay mClipboardOverlay;
    private ClipboardManager mClipboardManagerForUser;
    private KeyguardManager mKeyguardManagerForUser;

    private final UserTracker mUserTracker;
    private final Executor mMainExecutor;

    private final UserTracker.Callback mCallback = new UserTracker.Callback() {
        @Override
        public void onUserChanged(int newUser, @NonNull Context userContext) {
            UserTracker.Callback.super.onUserChanged(newUser, userContext);
            mClipboardManagerForUser.removePrimaryClipChangedListener(ClipboardListener.this);
            setUser(mUserTracker.getUserHandle());
            mClipboardManagerForUser.addPrimaryClipChangedListener(ClipboardListener.this);
        }
    };

    @Inject
    public ClipboardListener(Context context,
            Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
            ClipboardToast clipboardToast,
            UserTracker userTracker,
            UserScopedService<ClipboardManager> clipboardManager,
            KeyguardManager keyguardManager,
            UserScopedService<KeyguardManager> keyguardManager,
            UiEventLogger uiEventLogger,
            @Main Executor mainExecutor,
            ClipboardOverlaySuppressionController clipboardOverlaySuppressionController) {
        mContext = context;
        mOverlayProvider = clipboardOverlayControllerProvider;
        mClipboardToast = clipboardToast;
        mClipboardManager = clipboardManager.forUser(UserHandle.CURRENT);
        mKeyguardManager = keyguardManager;
        mClipboardManagerProvider = clipboardManager;
        mKeyguardManagerProvider = keyguardManager;
        mUiEventLogger = uiEventLogger;
        mClipboardOverlaySuppressionController = clipboardOverlaySuppressionController;

        mMainExecutor = mainExecutor;
        mUserTracker = userTracker;
        setUser(mUserTracker.getUserHandle());
    }

    private void setUser(UserHandle user) {
        mClipboardManagerForUser = mClipboardManagerProvider.forUser(user);
        mKeyguardManagerForUser = mKeyguardManagerProvider.forUser(user);
    }

    @Override
    public void start() {
        mClipboardManager.addPrimaryClipChangedListener(this);
        if (clipboardOverlayMultiuser()) {
            mUserTracker.addCallback(mCallback, mMainExecutor);
        }
        mClipboardManagerForUser.addPrimaryClipChangedListener(this);
    }

    @Override
    public void onPrimaryClipChanged() {
        if (!mClipboardManager.hasPrimaryClip()) {
        if (!mClipboardManagerForUser.hasPrimaryClip()) {
            return;
        }

        String clipSource = mClipboardManager.getPrimaryClipSource();
        ClipData clipData = mClipboardManager.getPrimaryClip();
        String clipSource = mClipboardManagerForUser.getPrimaryClipSource();
        ClipData clipData = mClipboardManagerForUser.getPrimaryClip();

        if (overrideSuppressOverlayCondition()) {
            if (mClipboardOverlaySuppressionController.shouldSuppressOverlay(clipData, clipSource,
@@ -112,7 +148,7 @@ public class ClipboardListener implements
        }

        // user should not access intents before setup or while device is locked
        if ((clipboardNoninteractiveOnLockscreen() && mKeyguardManager.isDeviceLocked())
        if ((clipboardNoninteractiveOnLockscreen() && mKeyguardManagerForUser.isDeviceLocked())
                || !isUserSetupComplete()
                || clipData == null // shouldn't happen, but just in case
                || clipData.getItemCount() == 0) {
+6 −0
Original line number Diff line number Diff line
@@ -427,6 +427,12 @@ public class FrameworkServicesModule {
        return context.getSystemService(KeyguardManager.class);
    }

    @Provides
    @Singleton
    static UserScopedService<KeyguardManager> provideKeyguardManagerUserScoped(Context context) {
        return new UserScopedServiceImpl<>(context, KeyguardManager.class);
    }

    @Provides
    @Singleton
    static LatencyTracker provideLatencyTracker(Context context) {
+112 −15
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.app.KeyguardManager;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.PersistableBundle;
import android.os.UserHandle;
@@ -45,6 +46,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.user.utils.FakeUserScopedService;

import org.junit.Before;
import org.junit.Test;
@@ -56,6 +59,7 @@ import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Provider;

@@ -68,6 +72,10 @@ public class ClipboardListenerTest extends SysuiTestCase {
    @Mock
    private KeyguardManager mKeyguardManager;
    @Mock
    private ClipboardManager mClipboardManagerSecondaryUser;
    @Mock
    private KeyguardManager mKeyguardManagerSecondaryUser;
    @Mock
    private ClipboardOverlayController mOverlayController;
    @Mock
    private ClipboardToast mClipboardToast;
@@ -76,9 +84,6 @@ public class ClipboardListenerTest extends SysuiTestCase {
    @Mock
    private ClipboardOverlaySuppressionController mClipboardOverlaySuppressionController;

    private ClipData mSampleClipData;
    private String mSampleSource = "Example source";

    @Captor
    private ArgumentCaptor<Runnable> mRunnableCaptor;
    @Captor
@@ -89,6 +94,20 @@ public class ClipboardListenerTest extends SysuiTestCase {
    @Spy
    private Provider<ClipboardOverlayController> mOverlayControllerProvider;

    private final FakeUserScopedService<ClipboardManager> mUserScopedClipboardManager =
            new FakeUserScopedService<>(mClipboardManager);
    private final FakeUserScopedService<KeyguardManager> mUserScopedKeyguardManager =
            new FakeUserScopedService<>(mKeyguardManager);
    private final FakeUserTracker mUserTracker = new FakeUserTracker();

    private final List<UserInfo> mUserInfos = List.of(
            new UserInfo(0, "system", 0), new UserInfo(50, "secondary", 0));
    private final ClipData mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
            new ClipData.Item("Test Item"));
    private final ClipData mSecondaryClipData = new ClipData(
            "Test secondary", new String[]{"text/plain"}, new ClipData.Item("Secondary Item"));
    private final String mSampleSource = "Example source";

    private ClipboardListener mClipboardListener;


@@ -97,30 +116,38 @@ public class ClipboardListenerTest extends SysuiTestCase {
        mOverlayControllerProvider = () -> mOverlayController;

        MockitoAnnotations.initMocks(this);
        when(mClipboardManager.hasPrimaryClip()).thenReturn(true);

        Settings.Secure.putInt(
                mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 1);

        mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
                new ClipData.Item("Test Item"));
        when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
        when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
        mUserTracker.set(mUserInfos, 0);
        UserHandle user0 = mUserInfos.get(0).getUserHandle();
        UserHandle user1 = mUserInfos.get(1).getUserHandle();
        mUserScopedKeyguardManager.addImplementation(user0, mKeyguardManager);
        mUserScopedKeyguardManager.addImplementation(user1, mKeyguardManagerSecondaryUser);
        setupClipboardManager(mClipboardManager, user0, mSampleClipData);
        setupClipboardManager(mClipboardManagerSecondaryUser, user1, mSecondaryClipData);

        mClipboardListener = new ClipboardListener(
                getContext(),
                mOverlayControllerProvider,
                mClipboardToast,
                user -> {
                    if (UserHandle.CURRENT.equals(user)) {
                        return mClipboardManager;
                    }
                    return null;
                },
                mKeyguardManager,
                mUserTracker,
                mUserScopedClipboardManager,
                mUserScopedKeyguardManager,
                mUiEventLogger,
                getContext().getMainExecutor(),
                mClipboardOverlaySuppressionController);
    }

    private void setupClipboardManager(
            ClipboardManager clipboardManager, UserHandle user, ClipData clipData) {
        when(clipboardManager.hasPrimaryClip()).thenReturn(true);
        when(clipboardManager.getPrimaryClip()).thenReturn(clipData);
        when(clipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
        mUserScopedClipboardManager.addImplementation(user, clipboardManager);
    }


    @Test
    public void test_initialization() {
@@ -159,6 +186,76 @@ public class ClipboardListenerTest extends SysuiTestCase {
        verify(mOverlayControllerProvider, times(2)).get();
    }

    @Test
    @DisableFlags(Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    public void test_noSwitchUserWithFlagOff() {
        mClipboardListener.start();

        mClipboardListener.onPrimaryClipChanged();
        mUserTracker.set(mUserInfos, 1);
        mUserTracker.onUserChanged(mUserInfos.get(1).id);
        mClipboardListener.onPrimaryClipChanged();

        verify(mKeyguardManager, times(2)).isDeviceLocked();
        verify(mClipboardManager, times(2)).hasPrimaryClip();
        verify(mOverlayController, times(2)).setClipData(mSampleClipData, mSampleSource);
        verifyNoMoreInteractions(mClipboardManagerSecondaryUser);
        verifyNoMoreInteractions(mKeyguardManagerSecondaryUser);
    }

    @Test
    @EnableFlags(Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    public void test_switchUserSwitchesClipboard() {
        mClipboardListener.start();

        mClipboardListener.onPrimaryClipChanged();
        verify(mClipboardManager).hasPrimaryClip();
        verify(mOverlayController).setClipData(mSampleClipData, mSampleSource);

        mUserTracker.set(mUserInfos, 1);
        mUserTracker.onUserChanged(mUserInfos.get(1).id);
        mClipboardListener.onPrimaryClipChanged();

        verify(mClipboardManagerSecondaryUser).hasPrimaryClip();
        verify(mOverlayController).setClipData(mSecondaryClipData, mSampleSource);
    }

    @Test
    @DisableFlags(Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
    @EnableFlags(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN)
    public void test_deviceLockedForSecondaryUser_withoutMultiuser_showsOverlay() {
        when(mKeyguardManager.isDeviceLocked()).thenReturn(false);
        when(mKeyguardManagerSecondaryUser.isDeviceLocked()).thenReturn(true);

        mClipboardListener.start();
        mUserTracker.set(mUserInfos, 1);
        mUserTracker.onUserChanged(mUserInfos.get(1).id);
        mClipboardListener.onPrimaryClipChanged();

        verify(mUiEventLogger, times(1)).log(
                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
        verify(mOverlayController).setClipData(mSampleClipData, mSampleSource);
        verifyNoMoreInteractions(mClipboardToast);
    }

    @Test
    @EnableFlags({Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER,
            Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN})
    public void test_deviceLockedForSecondaryUser_showsToast() {
        when(mKeyguardManager.isDeviceLocked()).thenReturn(false);
        when(mKeyguardManagerSecondaryUser.isDeviceLocked()).thenReturn(true);

        mClipboardListener.start();
        mUserTracker.set(mUserInfos, 1);
        mUserTracker.onUserChanged(mUserInfos.get(1).id);
        mClipboardListener.onPrimaryClipChanged();

        verify(mUiEventLogger, times(1)).log(
                ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
        verify(mClipboardToast, times(1)).showCopiedToast();
        verifyNoMoreInteractions(mOverlayControllerProvider);
    }

    @Test
    @DisableFlags(Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION)
    public void test_shouldSuppressOverlay() {