diff options
4 files changed, 174 insertions, 25 deletions
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a4b8821383e0..ff42ba378dc4 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -679,6 +679,16 @@ flag { } 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" description: "Show shared transitions from clipboard" diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 7033e641dc2c..c0c4ec335a34 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 78a8a42e2743..a15065736e45 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -429,6 +429,12 @@ public class FrameworkServicesModule { @Provides @Singleton + static UserScopedService<KeyguardManager> provideKeyguardManagerUserScoped(Context context) { + return new UserScopedServiceImpl<>(context, KeyguardManager.class); + } + + @Provides + @Singleton static LatencyTracker provideLatencyTracker(Context context) { return LatencyTracker.getInstance(context); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java index d8d53e006ab6..0c7989df7293 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java @@ -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() { @@ -160,6 +187,76 @@ public class ClipboardListenerTest extends SysuiTestCase { } @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() { // Regardless of the package or emulator, nothing should be suppressed without the flag |