diff options
| author | 2024-12-05 13:05:39 -0500 | |
|---|---|---|
| committer | 2024-12-16 09:53:23 -0500 | |
| commit | 6860d18582895f16ee46ed205668045d78778bc2 (patch) | |
| tree | fb1e058ea64f73ad817945ae1b3dae2e1243799a | |
| parent | d322e4e79b63d6a456ea9f6aa5921b9637194596 (diff) | |
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
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 |