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:
@@ -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(() -> {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user