diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 4805ed39e1a23..5f2b63089009c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -655,6 +655,13 @@ public class Bubble implements BubbleViewProvider { return mIsImportantConversation; } + /** + * Whether this bubble is conversation + */ + public boolean isConversation() { + return null != mShortcutInfo; + } + /** * Sets whether this notification should be suppressed in the shade. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index deb4fd5f19bbd..66241628fc779 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -33,7 +33,6 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; -import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -575,7 +574,7 @@ public class BubbleStackView extends FrameLayout if (maybeShowStackEdu()) { mShowedUserEducationInTouchListenerActive = true; return true; - } else if (isStackEduShowing()) { + } else if (isStackEduVisible()) { mStackEduView.hide(false /* fromExpansion */); } @@ -651,7 +650,7 @@ public class BubbleStackView extends FrameLayout mExpandedAnimationController.dragBubbleOut( v, viewInitialX + dx, viewInitialY + dy); } else { - if (isStackEduShowing()) { + if (isStackEduVisible()) { mStackEduView.hide(false /* fromExpansion */); } mStackAnimationController.moveStackFromTouch( @@ -733,8 +732,7 @@ public class BubbleStackView extends FrameLayout @Override public void onMove(float dx, float dy) { - if ((mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) - || isStackEduShowing()) { + if (isManageEduVisible() || isStackEduVisible()) { return; } @@ -996,7 +994,7 @@ public class BubbleStackView extends FrameLayout mStackAnimationController.updateResources(); mBubbleOverflow.updateResources(); - if (!isStackEduShowing() && mRelativeStackPositionBeforeRotation != null) { + if (!isStackEduVisible() && mRelativeStackPositionBeforeRotation != null) { mStackAnimationController.setStackPosition( mRelativeStackPositionBeforeRotation); mRelativeStackPositionBeforeRotation = null; @@ -1046,9 +1044,9 @@ public class BubbleStackView extends FrameLayout setOnClickListener(view -> { if (mShowingManage) { showManageMenu(false /* show */); - } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { + } else if (isManageEduVisible()) { mManageEduView.hide(); - } else if (isStackEduShowing()) { + } else if (isStackEduVisible()) { mStackEduView.hide(false /* isExpanding */); } else if (mBubbleData.isExpanded()) { mBubbleData.setExpanded(false); @@ -1246,11 +1244,20 @@ public class BubbleStackView extends FrameLayout updateManageButtonListener(); } + /** + * Whether the selected bubble is conversation bubble + */ + private boolean isConversationBubble() { + BubbleViewProvider bubble = mBubbleData.getSelectedBubble(); + return bubble instanceof Bubble && ((Bubble) bubble).isConversation(); + } + /** * Whether the educational view should show for the expanded view "manage" menu. */ private boolean shouldShowManageEdu() { - if (ActivityManager.isRunningInTestHarness()) { + if (!isConversationBubble()) { + // We only show user education for conversation bubbles right now return false; } final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION); @@ -1273,11 +1280,17 @@ public class BubbleStackView extends FrameLayout mManageEduView.show(mExpandedBubble.getExpandedView()); } + @VisibleForTesting + public boolean isManageEduVisible() { + return mManageEduView != null && mManageEduView.getVisibility() == VISIBLE; + } + /** * Whether education view should show for the collapsed stack. */ private boolean shouldShowStackEdu() { - if (ActivityManager.isRunningInTestHarness()) { + if (!isConversationBubble()) { + // We only show user education for conversation bubbles right now return false; } final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION); @@ -1310,13 +1323,14 @@ public class BubbleStackView extends FrameLayout return mStackEduView.show(mPositioner.getDefaultStartPosition()); } - private boolean isStackEduShowing() { + @VisibleForTesting + public boolean isStackEduVisible() { return mStackEduView != null && mStackEduView.getVisibility() == VISIBLE; } // Recreates & shows the education views. Call when a theme/config change happens. private void updateUserEdu() { - if (isStackEduShowing()) { + if (isStackEduVisible()) { removeView(mStackEduView); mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); addView(mStackEduView); @@ -1325,7 +1339,7 @@ public class BubbleStackView extends FrameLayout mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition()); mStackEduView.show(mPositioner.getDefaultStartPosition()); } - if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { + if (isManageEduVisible()) { removeView(mManageEduView); mManageEduView = new ManageEducationView(mContext, mPositioner); addView(mManageEduView); @@ -1429,7 +1443,7 @@ public class BubbleStackView extends FrameLayout mStackAnimationController.updateResources(); mDismissView.updateResources(); mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2); - if (!isStackEduShowing()) { + if (!isStackEduVisible()) { mStackAnimationController.setStackPosition( new RelativeStackPosition( mPositioner.getRestingPosition(), @@ -2013,7 +2027,7 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded) { if (mShowingManage) { showManageMenu(false); - } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { + } else if (isManageEduVisible()) { mManageEduView.hide(); } else { mBubbleData.setExpanded(false); @@ -2158,7 +2172,7 @@ public class BubbleStackView extends FrameLayout cancelDelayedExpandCollapseSwitchAnimations(); final boolean showVertically = mPositioner.showBubblesVertically(); mIsExpanded = true; - if (isStackEduShowing()) { + if (isStackEduVisible()) { mStackEduView.hide(true /* fromExpansion */); } beforeExpandedViewAnimation(); @@ -2280,7 +2294,7 @@ public class BubbleStackView extends FrameLayout private void animateCollapse() { cancelDelayedExpandCollapseSwitchAnimations(); - if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { + if (isManageEduVisible()) { mManageEduView.hide(); } @@ -2677,7 +2691,7 @@ public class BubbleStackView extends FrameLayout if (flyoutMessage == null || flyoutMessage.message == null || !bubble.showFlyout() - || isStackEduShowing() + || isStackEduVisible() || isExpanded() || mIsExpansionAnimating || mIsGestureInProgress @@ -2800,7 +2814,7 @@ public class BubbleStackView extends FrameLayout * them. */ public void getTouchableRegion(Rect outRect) { - if (isStackEduShowing()) { + if (isStackEduVisible()) { // When user education shows then capture all touches outRect.set(0, 0, getWidth(), getHeight()); return; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java index e8f3f69ca64e2..de967bfa288b2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java @@ -29,6 +29,8 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.res.Resources; import android.graphics.drawable.Icon; import android.os.Bundle; import android.service.notification.StatusBarNotification; @@ -162,4 +164,27 @@ public class BubbleTest extends ShellTestCase { verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any()); } + + @Test + public void testBubbleIsConversation_hasConversationShortcut() { + Bubble bubble = createBubbleWithShortcut(); + assertThat(bubble.getShortcutInfo()).isNotNull(); + assertThat(bubble.isConversation()).isTrue(); + } + + @Test + public void testBubbleIsConversation_hasNoShortcut() { + Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor); + assertThat(bubble.getShortcutInfo()).isNull(); + assertThat(bubble.isConversation()).isFalse(); + } + + private Bubble createBubbleWithShortcut() { + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext) + .setId("mockShortcutId") + .build(); + return new Bubble("mockKey", shortcutInfo, 10, Resources.ID_NULL, + "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */, + mMainExecutor, mBubbleMetadataFlagListener); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index cc3b4ab0fb4e5..652052736bed5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -57,6 +57,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -100,6 +101,7 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeWindowLogger; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.NotificationEntryHelper; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -135,6 +137,7 @@ import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubbleStackView; import com.android.wm.shell.bubbles.BubbleViewInfoTask; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.bubbles.StackEducationViewKt; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; @@ -1668,6 +1671,60 @@ public class BubblesTest extends SysuiTestCase { any(Bubble.class), anyBoolean(), anyBoolean()); } + @Test + public void testShowStackEdu_isNotConversationBubble() { + // Setup + setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false); + BubbleEntry bubbleEntry = createBubbleEntry(false /* isConversation */); + mBubbleController.updateBubble(bubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Click on bubble + Bubble bubble = mBubbleData.getBubbleInStackWithKey(bubbleEntry.getKey()); + assertFalse(bubble.isConversation()); + bubble.getIconView().callOnClick(); + + // Check education is not shown + BubbleStackView stackView = mBubbleController.getStackView(); + assertFalse(stackView.isStackEduVisible()); + } + + @Test + public void testShowStackEdu_isConversationBubble() { + // Setup + setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false); + BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */); + mBubbleController.updateBubble(bubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Click on bubble + Bubble bubble = mBubbleData.getBubbleInStackWithKey(bubbleEntry.getKey()); + assertTrue(bubble.isConversation()); + bubble.getIconView().callOnClick(); + + // Check education is shown + BubbleStackView stackView = mBubbleController.getStackView(); + assertTrue(stackView.isStackEduVisible()); + } + + @Test + public void testShowStackEdu_isSeenConversationBubble() { + // Setup + setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, true); + BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */); + mBubbleController.updateBubble(bubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Click on bubble + Bubble bubble = mBubbleData.getBubbleInStackWithKey(bubbleEntry.getKey()); + assertTrue(bubble.isConversation()); + bubble.getIconView().callOnClick(); + + // Check education is not shown + BubbleStackView stackView = mBubbleController.getStackView(); + assertFalse(stackView.isStackEduVisible()); + } + @Test public void testShowOrHideAppBubble_addsAndExpand() { assertThat(mBubbleController.isStackExpanded()).isFalse(); @@ -1816,6 +1873,20 @@ public class BubblesTest extends SysuiTestCase { mock(Bubbles.PendingIntentCanceledListener.class), new SyncExecutor()); } + private BubbleEntry createBubbleEntry(boolean isConversation) { + NotificationEntry notificationEntry = mNotificationTestHelper.createBubble(mDeleteIntent); + if (isConversation) { + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext) + .setId("shortcutId") + .build(); + NotificationEntryHelper.modifyRanking(notificationEntry) + .setIsConversation(true) + .setShortcutInfo(shortcutInfo) + .build(); + } + return mBubblesManager.notifToBubbleEntry(notificationEntry); + } + /** Creates a context that will return a PackageManager with specific AppInfo. */ private Context setUpContextWithPackageManager(String pkg, ApplicationInfo info) throws Exception { @@ -1852,6 +1923,15 @@ public class BubblesTest extends SysuiTestCase { bubbleMetadata.setFlags(flags); } + /** + * Set preferences boolean value for key + * Used to setup global state for stack view education tests + */ + private void setPrefBoolean(String key, boolean enabled) { + mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE) + .edit().putBoolean(key, enabled).apply(); + } + private Notification.BubbleMetadata getMetadata() { Intent target = new Intent(mContext, BubblesTestActivity.class); PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, FLAG_MUTABLE);