Merge "Fix launching bubbles from widgets" into sc-dev am: faee2e6b03

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14940550

Change-Id: I4912dc70ca3f130b43761848d1ad11d177c62ddf
This commit is contained in:
Mady Mellor
2021-06-15 16:11:57 +00:00
committed by Automerger Merge Worker
6 changed files with 193 additions and 13 deletions

View File

@@ -368,6 +368,13 @@ public class BubbleController {
return;
}
}
for (Bubble b : mBubbleData.getOverflowBubbles()) {
if (task.taskId == b.getTaskId()) {
promoteBubbleFromOverflow(b);
mBubbleData.setExpanded(true);
return;
}
}
}
});
@@ -815,7 +822,35 @@ public class BubbleController {
setIsBubble(bubble, true /* isBubble */);
}
@VisibleForTesting
/**
* Expands and selects the provided bubble as long as it already exists in the stack or the
* overflow.
*
* This is currently only used when opening a bubble via clicking on a conversation widget.
*/
public void expandStackAndSelectBubble(Bubble b) {
if (b == null) {
return;
}
if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
// already in the stack
mBubbleData.setSelectedBubble(b);
mBubbleData.setExpanded(true);
} else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
// promote it out of the overflow
promoteBubbleFromOverflow(b);
}
}
/**
* Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
* exists for this entry, and it is able to bubble, a new bubble will be created.
*
* This is the method to use when opening a bubble via a notification or in a state where
* the device might not be unlocked.
*
* @param entry the entry to use for the bubble.
*/
public void expandStackAndSelectBubble(BubbleEntry entry) {
if (mIsStatusBarShade) {
mNotifEntryToExpandOnShadeUnlock = null;
@@ -1382,6 +1417,21 @@ public class BubbleController {
});
}
@Override
public void expandStackAndSelectBubble(Bubble bubble) {
mMainExecutor.execute(() -> {
BubbleController.this.expandStackAndSelectBubble(bubble);
});
}
@Override
@Nullable
public Bubble getBubbleWithShortcutId(String shortcutId) {
return mMainExecutor.executeBlockingForResult(() -> {
return BubbleController.this.mBubbleData.getAnyBubbleWithShortcutId(shortcutId);
}, Bubble.class);
}
@Override
public void onTaskbarChanged(Bundle b) {
mMainExecutor.execute(() -> {

View File

@@ -26,6 +26,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.LocusId;
import android.content.pm.ShortcutInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -874,6 +875,34 @@ public class BubbleData {
return b;
}
/** @return any bubble (in the stack or the overflow) that matches the provided shortcutId. */
@Nullable
Bubble getAnyBubbleWithShortcutId(String shortcutId) {
if (TextUtils.isEmpty(shortcutId)) {
return null;
}
for (int i = 0; i < mBubbles.size(); i++) {
Bubble bubble = mBubbles.get(i);
String bubbleShortcutId = bubble.getShortcutInfo() != null
? bubble.getShortcutInfo().getId()
: bubble.getMetadataShortcutId();
if (shortcutId.equals(bubbleShortcutId)) {
return bubble;
}
}
for (int i = 0; i < mOverflowBubbles.size(); i++) {
Bubble bubble = mOverflowBubbles.get(i);
String bubbleShortcutId = bubble.getShortcutInfo() != null
? bubble.getShortcutInfo().getId()
: bubble.getMetadataShortcutId();
if (shortcutId.equals(bubbleShortcutId)) {
return bubble;
}
}
return null;
}
@VisibleForTesting(visibility = PRIVATE)
@Nullable
public Bubble getBubbleInStackWithKey(String key) {

View File

@@ -118,6 +118,19 @@ public interface Bubbles {
*/
void expandStackAndSelectBubble(BubbleEntry entry);
/**
* Request the stack expand if needed, then select the specified Bubble as current.
*
* @param bubble the bubble to be selected
*/
void expandStackAndSelectBubble(Bubble bubble);
/**
* @return a bubble that matches the provided shortcutId, if one exists.
*/
@Nullable
Bubble getBubbleWithShortcutId(String shortcutId);
/** Called for any taskbar changes. */
void onTaskbarChanged(Bundle b);

View File

@@ -35,9 +35,11 @@ import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubble;
import java.util.Optional;
@@ -53,14 +55,35 @@ public class LaunchConversationActivity extends Activity {
private final UserManager mUserManager;
private boolean mIsForTesting;
private IStatusBarService mIStatusBarService;
private CommandQueue mCommandQueue;
private Bubble mBubble;
private NotificationEntry mEntryToBubble;
@Inject
public LaunchConversationActivity(NotificationEntryManager notificationEntryManager,
Optional<BubblesManager> bubblesManagerOptional, UserManager userManager) {
Optional<BubblesManager> bubblesManagerOptional, UserManager userManager,
CommandQueue commandQueue) {
super();
mNotificationEntryManager = notificationEntryManager;
mBubblesManagerOptional = bubblesManagerOptional;
mUserManager = userManager;
mCommandQueue = commandQueue;
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
// (b/190833924) Wait for the app transition to finish before showing the bubble,
// opening the bubble while the transition is happening can mess with the placement
// of the bubble's surface.
@Override
public void appTransitionFinished(int displayId) {
if (mBubblesManagerOptional.isPresent()) {
if (mBubble != null) {
mBubblesManagerOptional.get().expandStackAndSelectBubble(mBubble);
} else if (mEntryToBubble != null) {
mBubblesManagerOptional.get().expandStackAndSelectBubble(mEntryToBubble);
}
}
mCommandQueue.removeCallback(this);
}
});
}
@Override
@@ -95,14 +118,28 @@ public class LaunchConversationActivity extends Activity {
return;
}
NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
notificationKey);
if (entry != null && entry.canBubble() && mBubblesManagerOptional.isPresent()) {
if (DEBUG) Log.d(TAG, "Open bubble for conversation");
mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
// Just opt-out and don't cancel the notification for bubbles.
finish();
return;
// We can potentially bubble without a notification, so rather than rely on
// notificationKey here (which could be null if there's no notification or if the
// bubble is suppressing the notification), so we'll use the shortcutId for lookups.
// This misses one specific case: a bubble that was never opened & still has a
// visible notification, but the bubble was dismissed & aged out of the overflow.
// So it wouldn't exist in the stack or overflow to be looked up BUT the notif entry
// would still exist & be bubbleable. So if we don't get a bubble from the
// shortcutId, fallback to notificationKey if it exists.
if (mBubblesManagerOptional.isPresent()) {
mBubble = mBubblesManagerOptional.get().getBubbleWithShortcutId(tileId);
NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
notificationKey);
if (mBubble != null || (entry != null && entry.canBubble())) {
mEntryToBubble = entry;
if (DEBUG) {
Log.d(TAG,
"Opening bubble: " + mBubble + ", entry: " + mEntryToBubble);
}
// Just opt-out and don't cancel the notification for bubbles.
finish();
return;
}
}
if (mIStatusBarService == null) {

View File

@@ -84,6 +84,7 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleEntry;
import com.android.wm.shell.bubbles.Bubbles;
@@ -657,6 +658,22 @@ public class BubblesManager implements Dumpable {
mBubbles.expandStackAndSelectBubble(notifToBubbleEntry(entry));
}
/**
* Request the stack expand if needed, then select the specified Bubble as current.
*
* @param bubble the bubble to be selected
*/
public void expandStackAndSelectBubble(Bubble bubble) {
mBubbles.expandStackAndSelectBubble(bubble);
}
/**
* @return a bubble that matches the provided shortcutId, if one exists.
*/
public Bubble getBubbleWithShortcutId(String shortcutId) {
return mBubbles.getBubbleWithShortcutId(shortcutId);
}
/** See {@link NotifCallback}. */
public void addNotifCallback(NotifCallback callback) {
mCallbacks.add(callback);

View File

@@ -16,11 +16,14 @@
package com.android.systemui.people.widget;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -39,9 +42,11 @@ import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubble;
import org.junit.Before;
import org.junit.Test;
@@ -87,6 +92,8 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
@Mock
private UserManager mUserManager;
private CommandQueue mCommandQueue;
@Captor
private ArgumentCaptor<NotificationVisibility> mNotificationVisibilityCaptor;
@@ -95,8 +102,9 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mCommandQueue = new CommandQueue(mContext);
mActivity = new LaunchConversationActivity(mNotificationEntryManager,
Optional.of(mBubblesManager), mUserManager);
Optional.of(mBubblesManager), mUserManager, mCommandQueue);
mActivity.setIsForTesting(true, mIStatusBarService);
mIntent = new Intent();
mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, "tile ID");
@@ -159,9 +167,13 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
mActivity.setIntent(mIntent);
mActivity.onCreate(new Bundle());
assertThat(mActivity.isFinishing()).isTrue();
mCommandQueue.appTransitionFinished(DEFAULT_DISPLAY);
verify(mIStatusBarService, times(1)).onNotificationClear(any(),
anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture());
verify(mBubblesManager, never()).expandStackAndSelectBubble(any());
verify(mBubblesManager, never()).expandStackAndSelectBubble(any(Bubble.class));
verify(mBubblesManager, never()).expandStackAndSelectBubble(any(NotificationEntry.class));
NotificationVisibility nv = mNotificationVisibilityCaptor.getValue();
assertThat(nv.count).isEqualTo(NOTIF_COUNT);
@@ -175,6 +187,9 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
mActivity.setIntent(mIntent);
mActivity.onCreate(new Bundle());
assertThat(mActivity.isFinishing()).isTrue();
mCommandQueue.appTransitionFinished(DEFAULT_DISPLAY);
// Don't clear the notification for bubbles.
verify(mIStatusBarService, never()).onNotificationClear(any(),
anyInt(), any(), anyInt(), anyInt(), any());
@@ -190,8 +205,27 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
mActivity.onCreate(new Bundle());
assertThat(mActivity.isFinishing()).isTrue();
mCommandQueue.appTransitionFinished(DEFAULT_DISPLAY);
verify(mIStatusBarService, never()).onNotificationClear(any(),
anyInt(), any(), anyInt(), anyInt(), any());
verify(mBubblesManager, never()).expandStackAndSelectBubble(any());
verify(mBubblesManager, never()).expandStackAndSelectBubble(any(Bubble.class));
verify(mBubblesManager, never()).expandStackAndSelectBubble(any(NotificationEntry.class));
}
@Test
public void testBubbleWithNoNotifOpensBubble() throws Exception {
Bubble bubble = mock(Bubble.class);
when(mBubblesManager.getBubbleWithShortcutId(any())).thenReturn(bubble);
mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
EMPTY_STRING);
mActivity.setIntent(mIntent);
mActivity.onCreate(new Bundle());
assertThat(mActivity.isFinishing()).isTrue();
mCommandQueue.appTransitionFinished(DEFAULT_DISPLAY);
verify(mBubblesManager, times(1)).expandStackAndSelectBubble(eq(bubble));
}
}