diff options
7 files changed, 237 insertions, 11 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java index 21a51d1096d5..c07d4022df76 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java @@ -18,13 +18,21 @@ package com.android.systemui.dreams.complication; import static com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent.DreamMediaEntryModule.DREAM_MEDIA_ENTRY_VIEW; import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_MEDIA_ENTRY_LAYOUT_PARAMS; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN; +import android.app.PendingIntent; import android.util.Log; import android.view.View; +import com.android.systemui.ActivityIntentHelper; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.media.MediaCarouselController; import com.android.systemui.media.dream.MediaDreamComplication; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -87,6 +95,15 @@ public class DreamMediaEntryComplication implements Complication { private final DreamOverlayStateController mDreamOverlayStateController; private final MediaDreamComplication mMediaComplication; + private final MediaCarouselController mMediaCarouselController; + + private final ActivityStarter mActivityStarter; + private final ActivityIntentHelper mActivityIntentHelper; + private final KeyguardStateController mKeyguardStateController; + private final NotificationLockscreenUserManager mLockscreenUserManager; + + private final FeatureFlags mFeatureFlags; + private boolean mIsTapToOpenEnabled; private boolean mMediaComplicationAdded; @@ -94,15 +111,28 @@ public class DreamMediaEntryComplication implements Complication { DreamMediaEntryViewController( @Named(DREAM_MEDIA_ENTRY_VIEW) View view, DreamOverlayStateController dreamOverlayStateController, - MediaDreamComplication mediaComplication) { + MediaDreamComplication mediaComplication, + MediaCarouselController mediaCarouselController, + ActivityStarter activityStarter, + ActivityIntentHelper activityIntentHelper, + KeyguardStateController keyguardStateController, + NotificationLockscreenUserManager lockscreenUserManager, + FeatureFlags featureFlags) { super(view); mDreamOverlayStateController = dreamOverlayStateController; mMediaComplication = mediaComplication; + mMediaCarouselController = mediaCarouselController; + mActivityStarter = activityStarter; + mActivityIntentHelper = activityIntentHelper; + mKeyguardStateController = keyguardStateController; + mLockscreenUserManager = lockscreenUserManager; + mFeatureFlags = featureFlags; mView.setOnClickListener(this::onClickMediaEntry); } @Override protected void onViewAttached() { + mIsTapToOpenEnabled = mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN); } @Override @@ -113,6 +143,31 @@ public class DreamMediaEntryComplication implements Complication { private void onClickMediaEntry(View v) { if (DEBUG) Log.d(TAG, "media entry complication tapped"); + if (mIsTapToOpenEnabled) { + final PendingIntent clickIntent = + mMediaCarouselController.getCurrentVisibleMediaContentIntent(); + + if (clickIntent == null) { + return; + } + + // See StatusBarNotificationActivityStarter#onNotificationClicked + final boolean showOverLockscreen = mKeyguardStateController.isShowing() + && mActivityIntentHelper.wouldShowOverLockscreen(clickIntent.getIntent(), + mLockscreenUserManager.getCurrentUserId()); + + if (showOverLockscreen) { + mActivityStarter.startActivity(clickIntent.getIntent(), + /* dismissShade */ true, + /* animationController */ null, + /* showOverLockscreenWhenLocked */ true); + } else { + mActivityStarter.postStartActivityDismissingKeyguard(clickIntent, null); + } + + return; + } + if (!mMediaComplicationAdded) { addMediaComplication(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 938332d75dd5..48f5f9eda909 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -200,7 +200,8 @@ public class Flags { public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901); public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903); public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904); - public static final UnreleasedFlag MEDIA_DREAM_COMPLICATION = new UnreleasedFlag(905); + public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905); + public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906); // 1000 - dock public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING = diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index e25f5dabe5a9..f8c6a5791839 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -1,5 +1,6 @@ package com.android.systemui.media +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.res.ColorStateList @@ -945,6 +946,11 @@ class MediaCarouselController @Inject constructor( mediaManager.onSwipeToDismiss() } + fun getCurrentVisibleMediaContentIntent(): PendingIntent? { + return MediaPlayerData.playerKeys() + .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.data?.clickIntent + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.apply { println("keysNeedRemoval: $keysNeedRemoval") diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index dc1488eefc4e..53b4d434bfcb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -16,7 +16,7 @@ package com.android.systemui.media.dream; -import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION; import android.content.Context; import android.util.Log; @@ -77,7 +77,7 @@ public class MediaDreamSentinel extends CoreStartable { public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey, @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency, boolean isSsReactivated) { - if (!mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)) { + if (!mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)) { return; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java index bc944404efe6..522b5b5a8530 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java @@ -16,17 +16,28 @@ package com.android.systemui.dreams.complication; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN; + import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.PendingIntent; +import android.content.Intent; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import androidx.test.filters.SmallTest; +import com.android.systemui.ActivityIntentHelper; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.media.MediaCarouselController; import com.android.systemui.media.dream.MediaDreamComplication; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; @@ -48,21 +59,52 @@ public class DreamMediaEntryComplicationTest extends SysuiTestCase { @Mock private MediaDreamComplication mMediaComplication; + @Mock + private MediaCarouselController mMediaCarouselController; + + @Mock + private ActivityStarter mActivityStarter; + + @Mock + private ActivityIntentHelper mActivityIntentHelper; + + @Mock + private KeyguardStateController mKeyguardStateController; + + @Mock + private NotificationLockscreenUserManager mLockscreenUserManager; + + @Mock + private FeatureFlags mFeatureFlags; + + @Mock + private PendingIntent mPendingIntent; + + private final Intent mIntent = new Intent("android.test.TEST_ACTION"); + private final Integer mCurrentUserId = 99; + @Before public void setup() { MockitoAnnotations.initMocks(this); + when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(false); } /** * Ensures clicking media entry chip adds/removes media complication. */ @Test - public void testClick() { + public void testClickToOpenUMO() { final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = new DreamMediaEntryComplication.DreamMediaEntryViewController( mView, mDreamOverlayStateController, - mMediaComplication); + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = ArgumentCaptor.forClass(View.OnClickListener.class); @@ -85,10 +127,90 @@ public class DreamMediaEntryComplicationTest extends SysuiTestCase { new DreamMediaEntryComplication.DreamMediaEntryViewController( mView, mDreamOverlayStateController, - mMediaComplication); + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); viewController.onViewDetached(); verify(mView).setSelected(false); verify(mDreamOverlayStateController).removeComplication(mMediaComplication); } + + /** + * Ensures clicking media entry chip opens media when flag is set. + */ + @Test + public void testClickToOpenMediaOverLockscreen() { + when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true); + + when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn( + mPendingIntent); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPendingIntent.getIntent()).thenReturn(mIntent); + when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId); + + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); + viewController.onViewAttached(); + + final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = + ArgumentCaptor.forClass(View.OnClickListener.class); + verify(mView).setOnClickListener(clickListenerCaptor.capture()); + + when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn( + true); + + clickListenerCaptor.getValue().onClick(mView); + verify(mActivityStarter).startActivity(mIntent, true, null, true); + } + + /** + * Ensures clicking media entry chip opens media when flag is set. + */ + @Test + public void testClickToOpenMediaDismissingLockscreen() { + when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true); + + when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn( + mPendingIntent); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPendingIntent.getIntent()).thenReturn(mIntent); + when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId); + + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); + viewController.onViewAttached(); + + final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = + ArgumentCaptor.forClass(View.OnClickListener.class); + verify(mView).setOnClickListener(clickListenerCaptor.capture()); + + when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn( + false); + + clickListenerCaptor.getValue().onClick(mView); + verify(mActivityStarter).postStartActivityDismissingKeyguard(mPendingIntent, null); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt index 5dd1cfcb76e4..e3e3b7413157 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.media +import android.app.PendingIntent import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -43,6 +44,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @@ -366,7 +368,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { playerIndex, mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex ) - assertEquals( playerIndex, 0) + assertEquals(playerIndex, 0) // Replaying the same media player one more time. // And check that the card stays in its position. @@ -402,4 +404,44 @@ class MediaCarouselControllerTest : SysuiTestCase() { visualStabilityCallback.value.onReorderingAllowed() assertEquals(true, result) } + + @Test + fun testGetCurrentVisibleMediaContentIntent() { + val clickIntent1 = mock(PendingIntent::class.java) + val player1 = Triple("player1", + DATA.copy(clickIntent = clickIntent1), + 1000L) + clock.setCurrentTimeMillis(player1.third) + MediaPlayerData.addMediaPlayer(player1.first, + player1.second.copy(notificationKey = player1.first), + panel, clock, isSsReactivated = false) + + assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1) + + val clickIntent2 = mock(PendingIntent::class.java) + val player2 = Triple("player2", + DATA.copy(clickIntent = clickIntent2), + 2000L) + clock.setCurrentTimeMillis(player2.third) + MediaPlayerData.addMediaPlayer(player2.first, + player2.second.copy(notificationKey = player2.first), + panel, clock, isSsReactivated = false) + + // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is + // added to the front because it was active more recently. + assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) + + val clickIntent3 = mock(PendingIntent::class.java) + val player3 = Triple("player3", + DATA.copy(clickIntent = clickIntent3), + 500L) + clock.setCurrentTimeMillis(player3.third) + MediaPlayerData.addMediaPlayer(player3.first, + player3.second.copy(notificationKey = player3.first), + panel, clock, isSsReactivated = false) + + // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is + // added to the end because it was active less recently. + assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index 0bfc0344b521..2f52950a9ee4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -16,7 +16,7 @@ package com.android.systemui.media.dream; -import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION; import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; @@ -68,7 +68,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(true); + when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(true); } @Test @@ -137,7 +137,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testOnMediaDataLoaded_mediaComplicationDisabled_doesNotAddComplication() { - when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false); + when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(false); final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); |