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

Commit 0acb11cf authored by Ivan Tkachenko's avatar Ivan Tkachenko
Browse files

Show Bubble OOBE on the first conversation bubble only

* Add `Bubble.isConversation` property and present Bubble OOBE views only when selected bubble is conversation bubble.

Test: manual
Test: atest BubbleTest BubblesTest
Bug: b/266466588
Change-Id: I16d0e37b4c39b3f308c49f2ecad335acc7f26839
parent 515f50b7
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -637,6 +637,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.
     */
+33 −19
Original line number Diff line number Diff line
@@ -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;
            }

@@ -994,7 +992,7 @@ public class BubbleStackView extends FrameLayout
                    mStackAnimationController.updateResources();
                    mBubbleOverflow.updateResources();

                    if (!isStackEduShowing() && mRelativeStackPositionBeforeRotation != null) {
                    if (!isStackEduVisible() && mRelativeStackPositionBeforeRotation != null) {
                        mStackAnimationController.setStackPosition(
                                mRelativeStackPositionBeforeRotation);
                        mRelativeStackPositionBeforeRotation = null;
@@ -1044,9 +1042,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);
@@ -1240,11 +1238,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);
@@ -1267,11 +1274,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);
@@ -1304,13 +1317,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);
@@ -1319,7 +1333,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);
@@ -1423,7 +1437,7 @@ public class BubbleStackView extends FrameLayout
        mStackAnimationController.updateResources();
        mDismissView.updateResources();
        mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2);
        if (!isStackEduShowing()) {
        if (!isStackEduVisible()) {
            mStackAnimationController.setStackPosition(
                    new RelativeStackPosition(
                            mPositioner.getRestingPosition(),
@@ -2007,7 +2021,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);
@@ -2152,7 +2166,7 @@ public class BubbleStackView extends FrameLayout
        cancelDelayedExpandCollapseSwitchAnimations();
        final boolean showVertically = mPositioner.showBubblesVertically();
        mIsExpanded = true;
        if (isStackEduShowing()) {
        if (isStackEduVisible()) {
            mStackEduView.hide(true /* fromExpansion */);
        }
        beforeExpandedViewAnimation();
@@ -2274,7 +2288,7 @@ public class BubbleStackView extends FrameLayout
    private void animateCollapse() {
        cancelDelayedExpandCollapseSwitchAnimations();

        if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
        if (isManageEduVisible()) {
            mManageEduView.hide();
        }

@@ -2671,7 +2685,7 @@ public class BubbleStackView extends FrameLayout
        if (flyoutMessage == null
                || flyoutMessage.message == null
                || !bubble.showFlyout()
                || isStackEduShowing()
                || isStackEduVisible()
                || isExpanded()
                || mIsExpansionAnimating
                || mIsGestureInProgress
@@ -2794,7 +2808,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;
+25 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+80 −0
Original line number Diff line number Diff line
@@ -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;
@@ -1645,6 +1648,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();
@@ -1774,6 +1831,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 {
@@ -1810,6 +1881,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);