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: 266466588 Change-Id: I16d0e37b4c39b3f308c49f2ecad335acc7f26839
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user